Background

In this report we will be delving into understanding survClust 1 via simulating various scenarios in the form of datasets. The choice of using simulated datasets makes the context of data neutral and the findings from survClust more accessible. We will go over outputs from survClust on various scenarios highlighting the complex nature of time-event data and performance of survClust.

Note that, in this report my goal is to get you excited about survClust and to walk you through the rigorous testing performed via simulations and design of simulations to iron out the details of the methodology.

For a deeper dive, please refer to the published manuscript with Genome Medicine. survClust can be downloaded from my GitHub repository

What is survClust?

Until now, sub-typing in cancer biology has relied heavily upon clustering/mining of molecular data alone, or unsupervised clustering of biological sequencing data such as gene expression data, DNA copy number data, gene mutation status etc 2. While this approach has been a cornerstone in understanding cancer disease biology, when the ultimate goal is to understand patient survival, a more focused approach with survival outcome is desired.

survClust is an outcome weighted integrative supervised clustering algorithm, designed to classify patients according to their molecular as well as time-event data or end point of interest such as Overall Survival (OS), Progression Free Survival (PFS) etc.

Below, we describe various scenarios where a supervised clustering approach such as survClust will be desired than more traditional unsupervised clustering approach such as kmeans.

Also, here is a quick primer by my colleague on survival data analysis.

Applications of survClust

Applications of survClust in cancer genomics have been established in previous work. See the list of scientific literature citing survClust

A possible extension could be say, we observe the life of Apple MacBook Pros purchased in the year 2012 from years 2013-2018. We define an event as a laptop failing entirely (coded as 1) where it couldn’t be revived by an Apple genius. If the MacBook is still functioning after 2018, we consider it as a non event, (coded as 0). If a MacBoook Pro drops from a study, or we are unable to keep track of their status aka loss of follow up, we will classify this event as censored.

Apart from this we also collect various features after 1yr of usage - battery health, processor chip details, number of alive pixels on the screen, tests that could measure performance etc.

We are interested in seeing if we can identify batches of MacBook Pros that have varied longevity (good or bad) based on the features that we collected.

This is a very broad example and a proper study design might require a deep dive into understanding more about Apple MacBook Pros. For example, definition of an event is crucial when analyzing a study with time-event data.

survClust workflow - the tldr version

survClust is available as an R package and is under the submission process with Bioconductor. Below we describe the proposed workflow of survClust method:

getDist - Compute a weighted distance matrix based on outcome across given m data types. Standardization across data types (binary, continuous) to facilitate the integration process and accounting for non-overlapping samples is also accomplished in this step.

combineDist - Integrate m data types by averaging over m weighted distance matrices.

survClust and cv_survclust - Cluster integrated weighted distance matrices via survClust. Optimal k is estimated via cross-validation using cv_survclust. Cross-validated results are assessed over the following performance metrics - the logrank statistic, standardized pooled within-cluster sum of squares (SPWSS) and small cluster solutions with size less than 5 samples.

Note:

  1. The input datatypes needs to be carefully pre-processed. See more details under the supplementary section of the manuscript.

  2. cv_survclust is a wrapper function that cross-validates and outputs cluster assignments. If you run without cross validation and just the commands on its own (getDist, combineDist and survClust), you are over-fitting!. Running multiple (at least 10) rounds of cross validation is strongly recommended to arrive at a stable solution. Also, the cross-validation step is computationally intensive and one can reduce run times by using parallel computing.

Read more about the performance metrics here and how to chose an optimal k

Logrank test statistic

The logrank test statistic3 is based on a non-parametric approach that quantifies survival difference between resulting subtypes and makes no assumption about the survival distributions.It tests the null hypothesis that there is no difference in survival between the groups.

The optimal k is the one with the maximum logrank statistic or at an inflection point where the statistic plateaus.

SPWSS

SPWSS is calculated as the pooled within-cluster sum of squares and standardized by the total sum of squares similar to methodology used in the gap statistic 4 to select the appropriate number of clusters.

The optimal number of clusters is where SPWSS is minimized and creates an “elbow” or a point of inflection, where addition of more clusters does not improve cluster separation. Since, this metric is standardized (lies between 0-1), it can also be used to compare individual runs of datasets that are measured over same samples.

Cluster Size

This metric computes the cluster size for each k across rounds of cross-validation. It records the instances where a round of cross validation returned a cluster size with less than 5 subjects. Such a solution might be unreliable and perhaps over-fitted for that k. An optimal k should have the least amount of such instances across the total cross validation rounds.

Data scenarios

Below we simulate 3 data scenarios to highlight the functionality of survClust. Survival times for demonstration purposes will also be simulated where we assume they are derived from an exponential distribution. Also, note that in addition to simulated dataset being context free, we can also predetermine a ground truth that could give us a fair estimate of our method’s performance.

A handy function to simulate input data and survival data is provided - simulate_survclust_data_fun.R.

Scenario 1

Here we will simulate a dataset with three clusters, a total of 150 (\(c_1=50, c_2=50, c_3=50\)) samples and 150 features such that:

\(c_1 \sim N(\mu = -1.5,\sigma=1)\)

\(c_2 \sim N(\mu = 0, \sigma=1)\) and

\(c_3 \sim N(\mu = 1.5, \sigma=1)\)

Note that, not all 150 features will be informative and we will only simulate about 15 (10%) features that will really drive the distinction between clusters. This is especially true in the context of human genome with about ~19,000 genes, where only a few of the genes will be actually associated with the disease biology and survival.

These clusters will also have distinct median survival of 4.5, 3.25, and 2 yrs respectively.

More realistically, from the features that are distinct some are going to be associated with survival and while others remain distinct with no association to survival. We will simulate half of the informative features as distinct and associated with survival and remaining half as just distinct.

fig:schematics of scenario1

fig:schematics of scenario1

#simulating scenario 1
mat_scenario1 <- list()

#fixing the solution to simulated scenario1
true_soln_scenario1 <- data.frame(samples = paste0("S",1:150) , 
                                  cluster = c(rep(1,50), rep(2,50), rep(3,50)))

#function from simulate_survclust_data_fun.R to simulate input data
mat_scenario1[[1]] <- simulate_data_type(k=3, 
                                         k_size = rep(50,3), n_features = 150,
                                         mu = c(-1.5, 0, 1.5), sd = rep(1,3),
                                         perc_inform = 0.10, my_seed = 112,
                                         permute = TRUE)
#simulating survival data 
surv_scenario1 <- simulate_surv_dat(k=3, 
                                    k_size = rep(50,3),
                                    med_surv_times = c(4.5, 3.25, 2),
                                    max_survival = 10, my_seed = 112)

Below we plot the distribution of our simulated 3-class clusters and the survival curves in a Kaplan Meier plot.

#long format to wrangle dta to plot ggridges and join the truth 
mat_scenario1_long <- mat_scenario1[[1]] %>%
    as.data.frame() %>%
    tibble::rownames_to_column(var = "samples") %>%
    tidyr::pivot_longer(., cols = -samples, names_to = "features") %>%
    left_join(., true_soln_scenario1, by ="samples") %>%
    mutate(cluster = as.factor(cluster))

#input data plot
p1 = ggplot(mat_scenario1_long, aes(x = value, y = cluster)) + geom_density_ridges(aes(fill = cluster), scale = 2, alpha = 0.6) + theme_bw() + scale_fill_manual(values = my_col_pal[1:3]) + ggtitle("cluster distribution of k=3 clusters, scenario1")

#KM plot
p2 = surv_scenario1 %>%
    tibble::rownames_to_column(var = "samples") %>%
    left_join(., true_soln_scenario1, by ="samples") %>%
    ggsurvplot(survfit(Surv(time, event) ~ cluster, data = .), data = ., 
               surv.median.line = "v", palette = my_col_pal) + 
    ggtitle("survival distribution across \n k=3 clusters, scenario1")
ggarrange(p1, (p2$plot + theme(plot.title = element_text(size = 13))), nrow=1)
# Picking joint bandwidth of 0.16

We see that since only 10% of the features are informative and 90% are noise, the overall distribution appears to be uniform for the three clusters.

The dashed vertical line from each survival curve represents the median survival rate for each group. See below the actual fit from survfit, as compared what we actually used in out simulation, \(\lambda_{c1} = 4.5, \lambda_{c2}=3.25, \lambda_{c3} = 2\)

#survfit to compute median survival times
fit <- surv_scenario1 %>%
    tibble::rownames_to_column(var = "samples") %>%
    left_join(., true_soln_scenario1, by ="samples") %>%
    survfit(Surv(time, event) ~ cluster, data = .)

fit_median <- summary(fit)$table[,'median'] 

data.frame(cluster = gsub("cluster=","c",names(fit_median)), 
           `median survival(yrs)` = round(fit_median,2)) %>% 
  gt::gt(caption = "median survival times are from `survfit` fit")
median survival times are from `survfit` fit
cluster median.survival.yrs.
c1 4.50
c2 3.34
c3 2.00

survClust run

Once the simulated data is assembled, we proceed to running survClust via cv_curvclust. Each k will be cross-validated across 3-folds for 10 rounds where fresh resampling for folds is done at every round. Computation across each k (where k = 2 to 7 clusters) will be parallelized using dopar from package doParallel.

This is wrapped up in a function by the name of sim_cvrounds, Expand the code section below.

sim_cvrounds<-function(kk, simdat, simsurvdat, fold=3, cv_rounds=10){
    this.fold<-fold
    fit<-list()
    for (i in seq_len(cv_rounds)){
        fit[[i]] <- survClust::cv_survclust(simdat, simsurvdat,kk,this.fold)
    }
    return(fit)
} 
registerDoParallel(cl <- makeCluster(6))

ptm <- Sys.time()

#each core performs computation for each k cluster
sim1_cv_fit <- foreach(kk=2:7) %dopar% sim_cvrounds(kk, simdat = mat_scenario1, simsurvdat = surv_scenario1)

ptm2 <- Sys.time()
stopCluster(cl)

tt = ptm2-ptm

The cross validation took 0.77 minutes to run.

Results and choosing k

All cross validated rounds of desired k clusters is then evaluated across 3 metrics -

  • Logrank test statistic
  • Standardized pooled within sum of squares (SPWSS)
  • Number of reliable solutions

More details on the metrics can be found here

In the plot below, the top-left plot is summarizing logrank test statistic over 10 rounds of cross-validated class labels across 3-fold cross-validation. Each dot is a solution from a cross validated round for that k. Here, we see that logrank peaks at k=3.

The top-right plot is summarizing SPWSS or Standardized Pooled Within Sum of Squares. It appears that SPWSS decreases monotonically as the number of clusters k increases. The optimal number of clusters is where SPWSS creates an “elbow” , here the plot elbows at k=3

The last plot, on the bottom-left, shows for each k how many k class labels have <=5 samples in 10 rounds of cross validation. In our case here, for k=3 the number of classes with <=5 samples is zero.

ss_stats <- getStats(sim1_cv_fit, kk=7, cvr=10)

p_lr <- plotStats_tidy(ss_stats$lr, highlight_k = 3)
p_lr <- p_lr + ylab("logrank test statistic")

p_spwss <- plotStats_tidy(ss_stats$spwss, highlight_k = 3)
p_spwss <- p_spwss + ylab("SPWSS")

p_clusize <- data.frame(k = paste0("k",2:7), cv_less_than_5 = ss_stats$bad.sol) %>%
    ggplot(aes(x=k, y = cv_less_than_5)) + 
    geom_line(col = "skyblue", group = "k") + geom_point() + 
    theme_minimal() + ylab("no. of cv solution with <=5 samples")
ggarrange(p_lr, p_spwss, p_clusize, nrow=2, ncol=2)

#cv_voting creates a consensus of all solutions across the 10 cross validated rounds 
k3 <- cv_voting(cv.fit = sim1_cv_fit,
                dat.dist = getDist(datasets = mat_scenario1,
                                   survdat = surv_scenario1), 
                pick_k=3)

k=3

From the metrics above, we choose k=3 as our optimal k. We can compare this with our simulated true solution -

#creating tab to store results 
tab <- true_soln_scenario1 %>% 
    left_join(., data.frame(sim_soln = k3, samples = paste0("S", 1:150)), 
              by = "samples")

tab %>% 
    dplyr::select(-samples) %>% 
    dplyr::rename("truth" = cluster) %>%
    gtsummary::tbl_summary(by = sim_soln) %>% modify_header(label = "**simulated solution**")
simulated solution 1, N = 531 2, N = 491 3, N = 481
truth


    1 50 (94%) 0 (0%) 0 (0%)
    2 3 (5.7%) 47 (96%) 0 (0%)
    3 0 (0%) 2 (4.1%) 48 (100%)
1 n (%)
ari <- round(mclust::adjustedRandIndex(tab$sim_soln, tab$cluster),2)

The concordance between true and survClust predicted solution can be computed by Rand Index.

The Rand Index is used to measure agreement between two classification labels. It has a maximum of 1 and a minimum of 0. Here 0 means the two data labels have no shared information and 1 means they are the same labels.

Conclusion

The concordance between the survClust reported solutions and truth class labels is high 0.9, meaning survClust did a good job in recovering the true solution when features carry a mixed signal. Also, note that, even though only 10% of the features were informative, we split this in half to simulate features that were distinct and associated with survival and survClust was able to borrow from the 5% of the informative features.

Scenario 2

survClust can perform clustering on more than one data type and can output results by integrating the datasets. In genomic context, these different datatypes are the different biological avenues such as gene expression data (continuous), gene mutation status (binary) etc. capturing different layers of the tumor biology.

Here we will simulate two dataset with three clusters, a total of 150 samples and 100 features with one dataset having strong clustering information and the other having weak clustering information. See below -

Dataset 1

Simulate three clusters (\(c_1=50, c_2=50, c_3=50\)) such that

\(c_1 \sim N(\mu = -1.5,\sigma=1)\),

\(c_2 \sim N(\mu = 0, \sigma=1)\) and

\(c_3 \sim N(\mu = 1.5, \sigma=1)\)

Dataset 2

Simulate three clusters (\(c_1=50, c_2=50, c_3=50\)) such that -

\(c_1 \sim N(\mu = -0.5,\sigma=1)\),

\(c_2 \sim N(\mu = 0, \sigma=1)\) and

\(c_3 \sim N(\mu = 0.5, \sigma=1)\)

Dataset2 can be interpreted as a modality that is adding noise to the modality of the data that explains the true survival association. For this scenario, the survival data remains same as scenario 1.

#scenario 2 set up
mat_scenario2 <- list()

true_soln_scenario2 <- true_soln_scenario1

#simulate data set but with strong cluster association 
mat_scenario2[[1]] <- simulate_data_type(k=3, 
                                         k_size = rep(50,3), 
                                         n_features = 100, 
                                         mu = c(-1.5, 0, 1.5), sd = rep(1,3),
                                         perc_inform = 0.10, my_seed = 123)


#simulate an additional data set but with weak cluster association 
mat_scenario2[[2]] <- simulate_data_type(k=3, 
                                         k_size = rep(50,3), 
                                         n_features = 150, 
                                         mu = c(-0.5, 0, 0.5), sd = rep(1,3),
                                         perc_inform = 0.10, my_seed = 123)

surv_scenario2 <- surv_scenario1

Below, we plot the distribution of only informative features across clusters across the two datasets. The dashed grey lines represent the means as we simulated across the two datasets.

#long form of dataset to visualize cluster distribution 
#dataset1
mat_scenario2_dat1_long <- mat_scenario2[[1]] %>%
    as.data.frame() %>%
    tibble::rownames_to_column(var = "samples") %>%
    tidyr::pivot_longer(., cols = -samples, names_to = "features") %>%
    left_join(., true_soln_scenario2, by ="samples") %>%
    mutate(cluster = as.factor(cluster))

p1 <- ggplot(mat_scenario2_dat1_long %>% 
                filter(features %in% paste0("F",1:10 )), aes(x = value, y = cluster)) + 
    geom_density_ridges(aes(fill = cluster), scale = 2, alpha = 0.6) + theme_bw() + scale_fill_manual(values = my_col_pal[1:3]) + 
    ggtitle("distribution of informative features only, dataset1, scenario2") 

p1 <- p1 +
    theme(plot.title = element_text(size=10)) + 
    geom_vline(xintercept = c(-1.5,0,1.5), col = "grey", linetype = "dashed")

#dataset2
mat_scenario2_dat2_long <- mat_scenario2[[2]] %>%
    as.data.frame() %>%
    tibble::rownames_to_column(var = "samples") %>%
    tidyr::pivot_longer(., cols = -samples, names_to = "features") %>%
    left_join(., true_soln_scenario2, by ="samples") %>%
    mutate(cluster = as.factor(cluster))

p2 <- ggplot(mat_scenario2_dat2_long %>% 
                filter(features %in% paste0("F",1:10 )), aes(x = value, y = cluster)) + 
    geom_density_ridges(aes(fill = cluster), scale = 2, alpha = 0.6) + theme_bw() + scale_fill_manual(values = my_col_pal[1:3]) + ggtitle("distribution of informative features only, dataset2, scenario2")

p2 <- p2 + 
    theme(plot.title = element_text(size=10)) +
    geom_vline(xintercept = c(-0.5,0,0.5), col = "grey", linetype = "dashed")

ggarrange(p1, p2, nrow=1)
# Picking joint bandwidth of 0.244
# Picking joint bandwidth of 0.244

survClust run

In this section we will be performing weighted integrative clustering survClust via previously defined function sim_cvrounds.

registerDoParallel(cl <- makeCluster(6))

ptm <- Sys.time()
sim2_cv_fit <- foreach(kk=2:7) %dopar% sim_cvrounds(kk, simdat = mat_scenario2, simsurvdat = surv_scenario2)
ptm2 <- Sys.time()

stopCluster(cl)

tt = ptm2-ptm

The cross validation took 0.96 minutes to run.

Results and choosing k

In the plot below, the top-left plot summarizing logrank test statistic over 10 rounds of cross-validated for each k, we see it is maximum for k=3. This is in concordance with SPWSS, where the point of inflection is also at k=3.

#code to see evaluate survClust run across logrank test stats, spwss

ss_stats <- getStats(sim2_cv_fit, kk=7, cvr=10)

p_lr <- plotStats_tidy(ss_stats$lr, highlight_k = 3)
p_lr <- p_lr + ylab("logrank test statistic")

p_spwss <- plotStats_tidy(ss_stats$spwss, highlight_k = 3)
p_spwss <- p_spwss + ylab("SPWSS")

p_clusize <- data.frame(k = paste0("k",2:7), cv_less_than_5 = ss_stats$bad.sol) %>%
    ggplot(aes(x=k, y = cv_less_than_5)) + geom_point() + geom_line(col = "skyblue", group="k") + theme_minimal() + ylab("cv solution with <=5 samples")
ggarrange(p_lr, p_spwss, p_clusize, nrow=2, ncol=2)

k3 <- cv_voting(cv.fit = sim2_cv_fit,
                dat.dist = getDist(datasets = mat_scenario2,
                                   survdat = surv_scenario2), 
                pick_k=3)

k=3

From the metrics above, we choose k=3 as our optimal k. We can compare this with our simulated true solution -

tab <- true_soln_scenario2 %>% 
    left_join(., data.frame(sim_soln = k3, samples = paste0("S", 1:150)), 
              by ="samples")

tab %>% 
    dplyr::select(-samples) %>% 
    dplyr::rename("truth" = cluster) %>%
    gtsummary::tbl_summary(by = sim_soln) %>% modify_header(label = "**simulated solution**")
simulated solution 1, N = 491 2, N = 481 3, N = 531
truth


    1 0 (0%) 0 (0%) 50 (94%)
    2 47 (96%) 0 (0%) 3 (5.7%)
    3 2 (4.1%) 48 (100%) 0 (0%)
1 n (%)
ari <- round(mclust::adjustedRandIndex(tab$sim_soln, tab$cluster),2)

The concordance between true and survClust predicted solution as computed by Rand Index is 0.9

Conclusion

  • In this scenario we performed an integrative supervised clustering. Note that, survClust can perform clustering on as many datasets as possible and consisting a mix of binary and continuous data types. In the published work, survClust performed integrative clustering on 6 data types.

  • The Rand Index is between survClust solution and the truth is 0.9 meaning even upon addition of a weaker data set, survClust is able to predict a solution close to truth without affecting its performance in the presence of noise.

Scenario 3

In this last scenario, we simulate two datasets with 175 samples and 200 features such that -

Data type 1 – strong clusters and weak survival association.

Datatype 2 – weak clusters and strong cluster association.

mat_scenario3 <- list()

#dataset 1
mat_scenario3[[1]] <- simulate_data_type(k=3, 
                                         k_size = c(50,50,75), 
                                         n_features = 200, 
                                         mu = c(0,-1.5, 1.5), sd = rep(1,3),
                                         perc_inform = 0.10)

#dataset 2
mat_scenario3[[2]] <- simulate_data_type(k=4, 
                                         k_size = c(50,50,30,45), 
                                         n_features = 200, 
                                         mu = c(0,-0.5, 0.5, 1.5), sd = rep(1,4), 
                                         perc_inform = 0.10)
#simulate survival data 
surv_scenario3 <- simulate_surv_dat(k=4, 
                                    k_size = c(50,50,30,45), 
                                    med_surv_times = c(5.5,4,2.5,1),
                                    max_survival = 10, my_seed=112)
#truth
true_soln_scenario3 <- data.frame(samples = paste0("S", 1:175), 
                                  cluster = c(rep(1,50), rep(2,50), rep(3,30), rep(4,45)))

Here the survival association is stronger with dataset2 and we simulate a survival data with 4 clusters. Below is the simulated survival distribution -

pp = surv_scenario3 %>%
    tibble::rownames_to_column(var = "samples") %>%
    left_join(., true_soln_scenario3, by ="samples") %>%
    ggsurvplot(survfit(Surv(time, event) ~ cluster, data = .), data = ., 
               surv.median.line = "v", palette = my_col_pal) + 
    ggtitle("survival distribution across \n k=4 clusters, scenario3") 

pp$plot + theme(plot.title = element_text(size = 11),
                legend.text = element_text(size=6))

The median survival rates of the simulated survival dataset from survfit model are below.

#median survival rate model fit 
fit <- surv_scenario3 %>%
    tibble::rownames_to_column(var = "samples") %>%
    left_join(., true_soln_scenario3, by ="samples") %>%
    survfit(Surv(time, event) ~ cluster, data = .)

fit_median <- summary(fit)$table[,'median'] 

data.frame(cluster = gsub("cluster=","c",names(fit_median)), 
           `median survival(yrs)` = round(fit_median,2)) %>% 
  gt::gt(caption = "median survival times are from `survfit` fit")
median survival times are from `survfit` fit
cluster median.survival.yrs.
c1 5.65
c2 4.11
c3 2.70
c4 1.08

survClust run

Similar, to previous section, cross validation was performed and parallelized for each k.

registerDoParallel(cl <- makeCluster(6))

ptm <- Sys.time()
sim3_cv_fit <- foreach(kk=2:7) %dopar% sim_cvrounds(kk, simdat = mat_scenario3, simsurvdat = surv_scenario3)
ptm2 <- Sys.time()

stopCluster(cl)
tt = ptm2-ptm

The cross validation took 2.74 minutes to run.

Results and choosing k

The logrank test statistic for this scenario is maximum at k=4 and we see that the SPWSS curve “elbows” at k=4. The cross validation round of k=4 also did not have any cluster sizes with <=5 samples. Thus, we will choose k=4 as the optimal k.

ss_stats <- getStats(sim3_cv_fit, kk=7, cvr=10)

p_lr <- plotStats_tidy(ss_stats$lr, highlight_k = 4)
p_lr <- p_lr + ylab("logrank test statistic")

p_spwss <- plotStats_tidy(ss_stats$spwss, highlight_k = 4)
p_spwss <- p_spwss + ylab("SPWSS")

p_clusize <- data.frame(k = paste0("k",2:7), cv_less_than_5 = ss_stats$bad.sol) %>%
    ggplot(aes(x=k, y = cv_less_than_5)) + geom_point() + geom_line(col = "skyblue", group="k") + theme_minimal() + ylab("cv solution with <=5 samples")
ggarrange(p_lr, p_spwss, p_clusize, nrow=2, ncol=2)

k4 <- cv_voting(cv.fit = sim3_cv_fit,
                dat.dist = getDist(datasets = mat_scenario3,
                                   survdat = surv_scenario3), 
                pick_k=4)

k=4

From the metrics above, we choose k=4 as our optimal k. We can compare this with our simulated true solution -

tab <- true_soln_scenario3 %>% 
    left_join(., data.frame(sim_soln = k4, samples = paste0("S", 1:175)), 
              by = "samples")

tab %>% 
    dplyr::select(-samples) %>% 
    dplyr::rename("truth" = cluster) %>%
    gtsummary::tbl_summary(by = sim_soln) %>% modify_header(label = "**simulated solution**")
simulated solution 1, N = 291 2, N = 501 3, N = 461 4, N = 501
truth



    1 0 (0%) 0 (0%) 0 (0%) 50 (100%)
    2 0 (0%) 50 (100%) 0 (0%) 0 (0%)
    3 29 (100%) 0 (0%) 1 (2.2%) 0 (0%)
    4 0 (0%) 0 (0%) 45 (98%) 0 (0%)
1 n (%)
ari <- round(mclust::adjustedRandIndex(tab$sim_soln, tab$cluster),2)

The concordance between true and survClust predicted solution as computed by Rand Index is 0.99

Conclusion

In this scenario we observe that survClust was able to arrive at solution showing good concordance with the truth even when weaker clusters show better association with survival.

Discussion

Here we demonstrated survClust, a supervised clustering methodology that can incorporate survival information as weights but also integrate data from multiple modalities. We looked at various scenarios and highlighted how survClust was able to predict solutions in high concordance with the true class labels that we simulated.

However, there were additional simulating strategies that one could further apply -

  1. At present a cluster contains features that are associated with survival in one direction only. In more thorough work towards the final publication, I simulated cluster structures that had features that had a mix of features that contributed to worse and better survival association.

  2. Simulating survival distribution on a small sample size is challenging and might violate the assumption of proportional hazards. This could explain why for some scenarios we are misclassifying some samples.

  3. set.seed functionality provided via my_seed parameter should be used carefully, as one might be duplicating data while simulating. For example, if we want to simulate 6 clusters and out of which 2 are similar in size and distribution but might vary in terms of survival, a case we often see with real data.

survClust continues to be used by many researchers in the scientific community.

Is integration always better?

survClust can be run individually in multiple datasets or in an integrated fashion across different modalities profiled for constant subjects. According to the nature and quality of these datasets, one might perform better or worse by integrating all the available information. We ran survClust on individual datasets from scenarios 2 and 3 and compare them with the integrated run as reported above.

registerDoParallel(cl <- makeCluster(6))

dat<-list()
dat[[1]] <- mat_scenario2[[1]]
sim2_cv_fit1 <- foreach(kk=2:7) %dopar% sim_cvrounds(kk, simdat = dat, simsurvdat = surv_scenario2)


dat<-list()
dat[[1]] <- mat_scenario2[[2]]
sim2_cv_fit2 <- foreach(kk=2:7) %dopar% sim_cvrounds(kk, simdat = dat, simsurvdat = surv_scenario2)


dat<-list()
dat[[1]] <- mat_scenario3[[1]]
sim3_cv_fit1 <- foreach(kk=2:7) %dopar% sim_cvrounds(kk, simdat = dat, simsurvdat = surv_scenario3)


dat<-list()
dat[[1]] <- mat_scenario3[[2]]
sim3_cv_fit2 <- foreach(kk=2:7) %dopar% sim_cvrounds(kk, simdat = dat, simsurvdat = surv_scenario3)

stopCluster(cl)

all_stats <- purrr::map(c("sim2_cv_fit1", "sim2_cv_fit2", "sim3_cv_fit1", "sim3_cv_fit2"), function(x) getStats(get(x), kk=7, cvr=10))

names(all_stats) = c("sim2_cv_fit1", "sim2_cv_fit2", "sim3_cv_fit1", "sim3_cv_fit2")

sim2_stats <- getStats(sim2_cv_fit, kk=7, cvr = 10)
sim3_stats <- getStats(sim3_cv_fit, kk=7, cvr=10)

scenario2_integ <- make_integrated_data(dat1 = all_stats$sim2_cv_fit1, dat2 = all_stats$sim2_cv_fit2, integ = sim2_stats)

scenario2_integ_plots <- make_integrated_plots(scenario2_integ)

pp <- ggarrange(plotlist = scenario2_integ_plots, ncol=1)
pp2 <- annotate_figure(pp, top = text_grob("Scenario2", face = "bold"))


scenario3_integ <- make_integrated_data(dat1 = all_stats$sim3_cv_fit1, dat2 = all_stats$sim3_cv_fit2, integ = sim3_stats)

scenario3_integ_plots <- make_integrated_plots(scenario3_integ)

pp <- ggarrange(plotlist = scenario3_integ_plots, ncol=1)
pp3 <- annotate_figure(pp, top = text_grob("Scenario3", face = "bold"))
ggarrange(pp2, pp3)

In the plots above, we plot the median value across cross validated rounds for each k for that particular metric (logrank, SPWSS etc.). The bars represent the 25th and 75th quantiles of the data.

In scenario 2, we added a noisy data type as dataset 2 and we see that dataset 2 performs worse than integrated and dataset1. Dataset1 carried the strong cluster distinction and is doing as well as integrated survClust solution in terms of peak logrank at k=3.

A similar trend is seen for scenario 3, where dataset1 had strong cluster separation but poor survival association with overall low median logrank values. Whereas, integrated solution and dataset2 are performing similar.

Overall, we see that integrated data type performs as well or slightly better than the dataset carrying the strong association with survival.

Session Info

#environemnt and package details 
pander::pander(sessionInfo())

R version 4.3.3 (2024-02-29)

Platform: aarch64-apple-darwin20 (64-bit)

locale: en_US.UTF-8||en_US.UTF-8||en_US.UTF-8||C||en_US.UTF-8||en_US.UTF-8

attached base packages: parallel, stats, graphics, grDevices, utils, datasets, methods and base

other attached packages: survClust(v.0.99.8), gtsummary(v.1.7.2), DT(v.0.33), ggridges(v.0.5.6), doParallel(v.1.0.17), iterators(v.1.0.14), foreach(v.1.5.2), survminer(v.0.4.9), ggpubr(v.0.6.0), survival(v.3.5-8), ggplot2(v.3.5.1), purrr(v.1.0.2), tidyr(v.1.3.1) and dplyr(v.1.1.4)

loaded via a namespace (and not attached): bitops(v.1.0-7), gridExtra(v.2.3), rlang(v.1.1.3), magrittr(v.2.0.3), matrixStats(v.1.3.0), compiler(v.4.3.3), png(v.0.1-8), vctrs(v.0.6.5), stringr(v.1.5.1), pkgconfig(v.2.0.3), crayon(v.1.5.2), fastmap(v.1.1.1), backports(v.1.4.1), XVector(v.0.42.0), labeling(v.0.4.3), pander(v.0.6.5), KMsurv(v.0.1-5), utf8(v.1.2.4), rmarkdown(v.2.26), markdown(v.1.12), ggbeeswarm(v.0.7.2), xfun(v.0.43), MultiAssayExperiment(v.1.28.0), zlibbioc(v.1.48.2), cachem(v.1.0.8), GenomeInfoDb(v.1.38.8), jsonlite(v.1.8.8), highr(v.0.10), DelayedArray(v.0.28.0), pdist(v.1.2.1), broom(v.1.0.5), R6(v.2.5.1), bslib(v.0.7.0), stringi(v.1.8.3), car(v.3.1-2), GenomicRanges(v.1.54.1), jquerylib(v.0.1.4), Rcpp(v.1.0.12), SummarizedExperiment(v.1.32.0), knitr(v.1.45), zoo(v.1.8-12), IRanges(v.2.36.0), Matrix(v.1.6-5), splines(v.4.3.3), tidyselect(v.1.2.1), rstudioapi(v.0.16.0), abind(v.1.4-5), yaml(v.2.3.8), codetools(v.0.2-20), lattice(v.0.22-6), tibble(v.3.2.1), Biobase(v.2.62.0), withr(v.3.0.0), evaluate(v.0.23), xml2(v.1.3.6), mclust(v.6.1.1), survMisc(v.0.5.6), pillar(v.1.9.0), MatrixGenerics(v.1.14.0), carData(v.3.0-5), stats4(v.4.3.3), generics(v.0.1.3), RCurl(v.1.98-1.14), S4Vectors(v.0.40.2), commonmark(v.1.9.1), munsell(v.0.5.1), scales(v.1.3.0), xtable(v.1.8-4), glue(v.1.7.0), tools(v.4.3.3), data.table(v.1.15.4), ggsignif(v.0.6.4), forcats(v.1.0.0), cowplot(v.1.1.3), grid(v.4.3.3), colorspace(v.2.1-0), GenomeInfoDbData(v.1.2.11), beeswarm(v.0.4.0), vipor(v.0.4.7), cli(v.3.6.2), km.ci(v.0.5-6), fansi(v.1.0.6), S4Arrays(v.1.2.1), broom.helpers(v.1.15.0), gt(v.0.10.1), gtable(v.0.3.5), rstatix(v.0.7.2), sass(v.0.4.9), digest(v.0.6.35), BiocGenerics(v.0.48.1), SparseArray(v.1.2.4), farver(v.2.1.1), htmlwidgets(v.1.6.4), htmltools(v.0.5.8.1) and lifecycle(v.1.0.4)

{
  # additional details 
  cat("\n\n")
  cat(sprintf("**Source:** *%s in %s project* \n", 
              knitr::current_input(dir=FALSE), tryCatch({rstudioapi::getActiveProject()}, 
                                                        error = function(e) {NA}) ))
  cat(sprintf("**Knit:** *%s by %s* \n", format(Sys.time(), '%Y-%m-%d %H:%M:%S%Z'), Sys.info()[["user"]] ))
  cat(tryCatch({sprintf("**Git remote:** *[%s](%s){target='_blank'}* [%s]\n", git2r::remote_url(), git2r::remote_url(), git2r::commits(git2r::repository("."), n=1)[[1]][["sha"]])}, error = function(e) {NULL}))
}

Source: project1.rmd in /Users/arshi/Documents/GitHub/projects_2024 project Knit: 2024-05-08 16:07:05EDT by arshi Git remote: https://github.com/arorarshi/projects_2024.git [6a7faa58406ef2af030d8ea98d03ae19d43e4341]


  1. Arora, A., et al. Pan-cancer identification of clinically relevant genomic subtypes using outcome-weighted integrative clustering. Genome Medicine 12.1(2020): 1-13↩︎

  2. Hoadley, Katherine A., et al. “Cell-of-origin patterns dominate the molecular classification of 10,000 tumors from 33 types of cancer.” Cell 173.2 (2018): 291-304↩︎

  3. Harrington DP, Fleming TR. A class of rank test procedures for censored survival-data. Biometrika. 1982;69:553–66.↩︎

  4. Tibshirani R, Walther G, Hastie T. Estimating the number of clusters in a data set via the gap statistic. J Roy Stat Soc B. 2001;63:411–23.↩︎

LS0tCnRpdGxlOiAic3VydkNsdXN0OiBhIHN1cGVydmlzZWQgY2x1c3RlcmluZyBhcHByb2FjaCBndWlkZWQgYnkgdGltZS1ldmVudCBkYXRhIgphdXRob3I6ICJBcnNoaSBBcm9yYSIKZGF0ZTogImByIGZvcm1hdChTeXMuRGF0ZSgpLCAnJUIgJWQsICVZJylgIgpvdXRwdXQ6IAogICAgaHRtbF9kb2N1bWVudDoKICAgICAgICBmaWdfY2FwdGlvbjogeWVzCiAgICAgICAgZGZfcHJpbnQ6IHBhZ2VkCiAgICAgICAgdG9jOiB0cnVlCiAgICAgICAgdG9jX2Zsb2F0OiB0cnVlCiAgICAgICAgY29kZV9mb2xkaW5nOiBoaWRlCiAgICAgICAgY29kZV9kb3dubG9hZDogdHJ1ZQpudW1iZXJzZWN0aW9uczogeWVzCi0tLQoKYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9CmtuaXRyOjpvcHRzX2NodW5rJHNldCgKICAgIGV2YWw9VFJVRSwKICAgIGVjaG8gPSBUUlVFLAogICAgbWVzc2FnZT1UUlVFLAogICAgY2FjaGU9RkFMU0UsIAogICAgd2FybmluZyA9IFRSVUUsIAogICAgZXJyb3IgPSBGQUxTRSwgCiAgICBjb21tZW50ID0gIiMiLCAKICAgIHRpZHkgPSBGQUxTRSwgCiAgICByZXN1bHRzID0gImFzaXMiLCAKICAgIGZpZy53aWR0aCA9IDUsIAogICAgZmlnLmhlaWdodD01KQprbml0cjo6b3B0c19rbml0JHNldCgKICAgIHJvb3QuZGlyID0gIi4iKQpvcHRpb25zKHdhcm49MSwgd2lkdGg9MjAwKQoKI3BhY2thZ2VzCmxpYnJhcnkoZHBseXIpCmxpYnJhcnkodGlkeXIpCmxpYnJhcnkocHVycnIpCmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeShzdXJ2aXZhbCkKbGlicmFyeShzdXJ2bWluZXIpCgpsaWJyYXJ5KGRvUGFyYWxsZWwpCmxpYnJhcnkoZ2dyaWRnZXMpCmxpYnJhcnkoRFQpCmxpYnJhcnkoZ3RzdW1tYXJ5KQoKbGlicmFyeShzdXJ2Q2x1c3QpCgojIGxpYnJhcnkoZ2l0MnIpCiMgbGlicmFyeShwYW5kZXIpCgojciBzY3JpcHQgd2l0aCBuZWNlc3NhcnkgZnVuY3Rpb25zIApzb3VyY2UoInNjcmlwdHMvc2ltdWxhdGVfc3VydmNsdXN0X2RhdGFfZnVuLlIiKQpzb3VyY2UoInNjcmlwdHMvdXRpbHMuUiIpCgpteV9jb2xfcGFsIDwtIGMoImRhcmtraGFraSIsImRhcmttYWdlbnRhIiwiZmlyZWJyaWNrMiIsImNvcm5mbG93ZXJibHVlIiwiZ3JlZW4iLCJkYXJrYmx1ZSIsImJsYWNrIiwiaG90cGluazIiKQpgYGAKCiMgQmFja2dyb3VuZAoKSW4gdGhpcyByZXBvcnQgd2Ugd2lsbCBiZSBkZWx2aW5nIGludG8gdW5kZXJzdGFuZGluZyAqKnN1cnZDbHVzdCoqIFteMV0gdmlhIHNpbXVsYXRpbmcgdmFyaW91cyBzY2VuYXJpb3MgaW4gdGhlIGZvcm0gb2YgZGF0YXNldHMuIFRoZSBjaG9pY2Ugb2YgdXNpbmcgc2ltdWxhdGVkIGRhdGFzZXRzIG1ha2VzIHRoZSBjb250ZXh0IG9mIGRhdGEgbmV1dHJhbCBhbmQgdGhlIGZpbmRpbmdzIGZyb20gc3VydkNsdXN0IG1vcmUgYWNjZXNzaWJsZS4gCldlIHdpbGwgZ28gb3ZlciBvdXRwdXRzIGZyb20gc3VydkNsdXN0IG9uIHZhcmlvdXMgc2NlbmFyaW9zIGhpZ2hsaWdodGluZyB0aGUgY29tcGxleCBuYXR1cmUgb2YgdGltZS1ldmVudCBkYXRhIGFuZCBwZXJmb3JtYW5jZSBvZiBzdXJ2Q2x1c3QuCgpOb3RlIHRoYXQsIGluIHRoaXMgcmVwb3J0IG15IGdvYWwgaXMgdG8gZ2V0IHlvdSBleGNpdGVkIGFib3V0IHN1cnZDbHVzdCBhbmQgdG8gd2FsayB5b3UgdGhyb3VnaCB0aGUgcmlnb3JvdXMgdGVzdGluZyBwZXJmb3JtZWQgdmlhIHNpbXVsYXRpb25zIGFuZCBkZXNpZ24gb2Ygc2ltdWxhdGlvbnMgdG8gaXJvbiBvdXQgdGhlIGRldGFpbHMgb2YgdGhlIG1ldGhvZG9sb2d5LiAgCgpGb3IgYSBkZWVwZXIgZGl2ZSwgcGxlYXNlIHJlZmVyIHRvIHRoZSBbcHVibGlzaGVkIG1hbnVzY3JpcHQgd2l0aCAqR2Vub21lIE1lZGljaW5lKl0oIDEwLjExODYvczEzMDczLTAyMC0wMDgwNC04KS4gc3VydkNsdXN0IGNhbiBiZSBkb3dubG9hZGVkIGZyb20gbXkgW0dpdEh1YiByZXBvc2l0b3J5XShodHRwczovL2dpdGh1Yi5jb20vYXJvcmFyc2hpL3N1cnZDbHVzdCkKCmBgYHtyLCBlY2hvPUZBTFNFLCBvdXQud2lkdGg9JzQwJScsIG91dC5leHRyYT0nc3R5bGU9ImZsb2F0OnJpZ2h0OyBwYWRkaW5nOjEwcHgiJ30Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoImltYWdlcy9pbnRyb19zdXJ2Y2x1c3QucG5nIikKYGBgCgojIyBXaGF0IGlzIHN1cnZDbHVzdD8gCgpVbnRpbCBub3csIHN1Yi10eXBpbmcgaW4gY2FuY2VyIGJpb2xvZ3kgaGFzIHJlbGllZCBoZWF2aWx5IHVwb24gY2x1c3RlcmluZy9taW5pbmcgb2YgbW9sZWN1bGFyIGRhdGEgYWxvbmUsIG9yIHVuc3VwZXJ2aXNlZCBjbHVzdGVyaW5nIG9mIGJpb2xvZ2ljYWwgc2VxdWVuY2luZyBkYXRhIHN1Y2ggYXMgZ2VuZSBleHByZXNzaW9uIGRhdGEsIEROQSBjb3B5IG51bWJlciBkYXRhLCBnZW5lIG11dGF0aW9uIHN0YXR1cyBldGMgW14yXS4gV2hpbGUgdGhpcyBhcHByb2FjaCBoYXMgYmVlbiBhIGNvcm5lcnN0b25lIGluIHVuZGVyc3RhbmRpbmcgY2FuY2VyIGRpc2Vhc2UgYmlvbG9neSwgd2hlbiB0aGUgdWx0aW1hdGUgZ29hbCBpcyB0byB1bmRlcnN0YW5kIHBhdGllbnQgc3Vydml2YWwsIGEgbW9yZSBmb2N1c2VkIGFwcHJvYWNoIHdpdGggc3Vydml2YWwgb3V0Y29tZSBpcyBkZXNpcmVkLiAgIAoKc3VydkNsdXN0IGlzIGFuIG91dGNvbWUgd2VpZ2h0ZWQgaW50ZWdyYXRpdmUgc3VwZXJ2aXNlZCBjbHVzdGVyaW5nIGFsZ29yaXRobSwgZGVzaWduZWQgdG8gY2xhc3NpZnkgcGF0aWVudHMgYWNjb3JkaW5nIHRvIHRoZWlyIG1vbGVjdWxhciBhcyB3ZWxsIGFzIHRpbWUtZXZlbnQgZGF0YSBvciBlbmQgcG9pbnQgb2YgaW50ZXJlc3Qgc3VjaCBhcyBPdmVyYWxsIFN1cnZpdmFsIChPUyksIFByb2dyZXNzaW9uIEZyZWUgU3Vydml2YWwgKFBGUykgZXRjLiAKCkJlbG93LCB3ZSBkZXNjcmliZSB2YXJpb3VzIHNjZW5hcmlvcyB3aGVyZSBhIHN1cGVydmlzZWQgY2x1c3RlcmluZyBhcHByb2FjaCBzdWNoIGFzIHN1cnZDbHVzdCB3aWxsIGJlIGRlc2lyZWQgdGhhbiBtb3JlIHRyYWRpdGlvbmFsIHVuc3VwZXJ2aXNlZCBjbHVzdGVyaW5nIGFwcHJvYWNoIHN1Y2ggYXMgYGttZWFuc2AuIAoKQWxzbywgaGVyZSAgaXMgYSBxdWljayBbcHJpbWVyXShodHRwczovL3d3dy5lbWlseXphYm9yLmNvbS90dXRvcmlhbHMvc3Vydml2YWxfYW5hbHlzaXNfaW5fcl90dXRvcmlhbC5odG1sKSBieSBteSBjb2xsZWFndWUgb24gc3Vydml2YWwgZGF0YSBhbmFseXNpcy4gIAoKIyMgQXBwbGljYXRpb25zIG9mIHN1cnZDbHVzdAoKQXBwbGljYXRpb25zIG9mIHN1cnZDbHVzdCBpbiBjYW5jZXIgZ2Vub21pY3MgaGF2ZSBiZWVuIGVzdGFibGlzaGVkIGluIHByZXZpb3VzIHdvcmsuIFNlZSB0aGUgbGlzdCBvZiBzY2llbnRpZmljIGxpdGVyYXR1cmUgY2l0aW5nIFsqKnN1cnZDbHVzdCoqXShodHRwczovL3NjaG9sYXIuZ29vZ2xlLmNvbS9zY2hvbGFyP29pPWJpYnMmaGw9ZW4mY2l0ZXM9NTgxOTk4MDc5MzcwNjIxNzYwMCkKCkEgcG9zc2libGUgZXh0ZW5zaW9uIGNvdWxkIGJlIHNheSwgd2Ugb2JzZXJ2ZSB0aGUgbGlmZSBvZiBBcHBsZSBNYWNCb29rIFByb3MgcHVyY2hhc2VkIGluIHRoZSB5ZWFyIDIwMTIgZnJvbSB5ZWFycyAyMDEzLTIwMTguIApXZSBkZWZpbmUgYW4gZXZlbnQgYXMgYSBsYXB0b3AgZmFpbGluZyBlbnRpcmVseSAoY29kZWQgYXMgMSkgd2hlcmUgaXQgY291bGRuJ3QgYmUgcmV2aXZlZCBieSBhbiBBcHBsZSBnZW5pdXMuIElmIHRoZSBNYWNCb29rIGlzIHN0aWxsIGZ1bmN0aW9uaW5nIGFmdGVyIDIwMTgsIHdlIGNvbnNpZGVyIGl0IGFzIGEgbm9uIGV2ZW50LCAoY29kZWQgYXMgMCkuIElmIGEgTWFjQm9vb2sgUHJvIGRyb3BzIGZyb20gYSBzdHVkeSwgb3Igd2UgYXJlIHVuYWJsZSB0byBrZWVwIHRyYWNrIG9mIHRoZWlyIHN0YXR1cyBha2EgbG9zcyBvZiBmb2xsb3cgdXAsIHdlIHdpbGwgY2xhc3NpZnkgdGhpcyBldmVudCBhcyBjZW5zb3JlZC4gCgpBcGFydCBmcm9tIHRoaXMgd2UgYWxzbyBjb2xsZWN0IHZhcmlvdXMgZmVhdHVyZXMgYWZ0ZXIgMXlyIG9mIHVzYWdlIC0gYmF0dGVyeSBoZWFsdGgsIHByb2Nlc3NvciBjaGlwIGRldGFpbHMsIG51bWJlciBvZiBhbGl2ZSBwaXhlbHMgb24gdGhlIHNjcmVlbiwgdGVzdHMgdGhhdCBjb3VsZCBtZWFzdXJlIHBlcmZvcm1hbmNlIGV0Yy4gCgpXZSBhcmUgaW50ZXJlc3RlZCBpbiBzZWVpbmcgaWYgd2UgY2FuIGlkZW50aWZ5IGJhdGNoZXMgb2YgTWFjQm9vayBQcm9zIHRoYXQgaGF2ZSB2YXJpZWQgbG9uZ2V2aXR5IChnb29kIG9yIGJhZCkgYmFzZWQgb24gdGhlIGZlYXR1cmVzIHRoYXQgd2UgY29sbGVjdGVkLiAKClRoaXMgaXMgYSB2ZXJ5IGJyb2FkIGV4YW1wbGUgYW5kIGEgcHJvcGVyIHN0dWR5IGRlc2lnbiBtaWdodCByZXF1aXJlIGEgZGVlcCBkaXZlIGludG8gdW5kZXJzdGFuZGluZyBtb3JlIGFib3V0IEFwcGxlIE1hY0Jvb2sgUHJvcy4gRm9yIGV4YW1wbGUsIGRlZmluaXRpb24gb2YgYW4gZXZlbnQgaXMgY3J1Y2lhbCB3aGVuIGFuYWx5emluZyBhIHN0dWR5IHdpdGggdGltZS1ldmVudCBkYXRhLiAKCgojIyBzdXJ2Q2x1c3Qgd29ya2Zsb3cgLSB0aGUgdGxkciB2ZXJzaW9uIHsjd2Zsb3d9CgpzdXJ2Q2x1c3QgaXMgYXZhaWxhYmxlIGFzIGFuIFIgcGFja2FnZSBhbmQgaXMgdW5kZXIgdGhlIHN1Ym1pc3Npb24gcHJvY2VzcyB3aXRoIFtCaW9jb25kdWN0b3JdKGh0dHBzOi8vd3d3LmJpb2NvbmR1Y3Rvci5vcmcpLiBCZWxvdyB3ZSBkZXNjcmliZSB0aGUgcHJvcG9zZWQgd29ya2Zsb3cgb2Ygc3VydkNsdXN0IG1ldGhvZDoKCmBgYHtyLCBlY2hvPUZBTFNFfQprbml0cjo6aW5jbHVkZV9ncmFwaGljcygiaW1hZ2VzL3N1cnZDbHVzdF93b3JrZmxvdy5wbmciKQpgYGAKCmBnZXREaXN0YCAtIENvbXB1dGUgYSB3ZWlnaHRlZCBkaXN0YW5jZSBtYXRyaXggYmFzZWQgb24gb3V0Y29tZSBhY3Jvc3MgZ2l2ZW4gYG1gIGRhdGEgdHlwZXMuIFN0YW5kYXJkaXphdGlvbiBhY3Jvc3MgZGF0YSB0eXBlcyAoYmluYXJ5LCBjb250aW51b3VzKSB0byBmYWNpbGl0YXRlIHRoZSBpbnRlZ3JhdGlvbiBwcm9jZXNzIGFuZCBhY2NvdW50aW5nIGZvciBub24tb3ZlcmxhcHBpbmcgc2FtcGxlcyBpcyBhbHNvIGFjY29tcGxpc2hlZCBpbiB0aGlzIHN0ZXAuCgpgY29tYmluZURpc3RgIC0gSW50ZWdyYXRlIGBtYCBkYXRhIHR5cGVzIGJ5IGF2ZXJhZ2luZyBvdmVyIGBtYCB3ZWlnaHRlZCBkaXN0YW5jZSBtYXRyaWNlcy4KCmBzdXJ2Q2x1c3RgIGFuZCBgY3Zfc3VydmNsdXN0YCAtIENsdXN0ZXIgaW50ZWdyYXRlZCB3ZWlnaHRlZCBkaXN0YW5jZSBtYXRyaWNlcyB2aWEgYHN1cnZDbHVzdGAuIE9wdGltYWwgYGtgIGlzIGVzdGltYXRlZCB2aWEgY3Jvc3MtdmFsaWRhdGlvbiB1c2luZyBgY3Zfc3VydmNsdXN0YC4gQ3Jvc3MtdmFsaWRhdGVkIHJlc3VsdHMgYXJlIGFzc2Vzc2VkIG92ZXIgdGhlIGZvbGxvd2luZyBwZXJmb3JtYW5jZSBtZXRyaWNzIC0gdGhlICoqbG9ncmFuayBzdGF0aXN0aWMsIHN0YW5kYXJkaXplZCBwb29sZWQgd2l0aGluLWNsdXN0ZXIgc3VtIG9mIHNxdWFyZXMgKFNQV1NTKSoqIGFuZCBzbWFsbCBjbHVzdGVyIHNvbHV0aW9ucyB3aXRoICoqc2l6ZSBsZXNzIHRoYW4gNSBzYW1wbGVzKiouCgoqKipOb3RlOiAqKioKCigxKSBUaGUgaW5wdXQgZGF0YXR5cGVzIG5lZWRzIHRvIGJlIGNhcmVmdWxseSBwcmUtcHJvY2Vzc2VkLiBTZWUgbW9yZSBkZXRhaWxzIHVuZGVyIHRoZSBzdXBwbGVtZW50YXJ5IHNlY3Rpb24gb2YgdGhlIG1hbnVzY3JpcHQuICAKCigyKSBgY3Zfc3VydmNsdXN0YCBpcyBhIHdyYXBwZXIgZnVuY3Rpb24gdGhhdCBjcm9zcy12YWxpZGF0ZXMgYW5kIG91dHB1dHMgY2x1c3RlciBhc3NpZ25tZW50cy4gSWYgeW91IHJ1biB3aXRob3V0IGNyb3NzIHZhbGlkYXRpb24gYW5kIGp1c3QgdGhlIGNvbW1hbmRzIG9uIGl0cyBvd24gKGBnZXREaXN0YCwgYGNvbWJpbmVEaXN0YCBhbmQgYHN1cnZDbHVzdGApLCB5b3UgYXJlICoqb3Zlci1maXR0aW5nISoqLiBSdW5uaW5nIG11bHRpcGxlIChhdCBsZWFzdCAxMCkgcm91bmRzIG9mIGNyb3NzIHZhbGlkYXRpb24gaXMgc3Ryb25nbHkgcmVjb21tZW5kZWQgdG8gYXJyaXZlIGF0IGEgc3RhYmxlIHNvbHV0aW9uLiBBbHNvLCB0aGUgY3Jvc3MtdmFsaWRhdGlvbiBzdGVwIGlzIGNvbXB1dGF0aW9uYWxseSBpbnRlbnNpdmUgYW5kIG9uZSBjYW4gcmVkdWNlIHJ1biB0aW1lcyBieSB1c2luZyBwYXJhbGxlbCBjb21wdXRpbmcuIAoKPGRldGFpbHM+Cgo8c3VtbWFyeT5SZWFkIG1vcmUgYWJvdXQgdGhlIHBlcmZvcm1hbmNlIG1ldHJpY3MgaGVyZSBhbmQgaG93IHRvIGNob3NlIGFuIG9wdGltYWwgYGtgPC9zdW1tYXJ5PgoKKipMb2dyYW5rIHRlc3Qgc3RhdGlzdGljKioKClRoZSBsb2dyYW5rIHRlc3Qgc3RhdGlzdGljW14zXSBpcyBiYXNlZCBvbiBhIG5vbi1wYXJhbWV0cmljIGFwcHJvYWNoIHRoYXQgcXVhbnRpZmllcyBzdXJ2aXZhbCBkaWZmZXJlbmNlIGJldHdlZW4gcmVzdWx0aW5nIHN1YnR5cGVzIGFuZCBtYWtlcyBubyBhc3N1bXB0aW9uIGFib3V0IHRoZSBzdXJ2aXZhbCBkaXN0cmlidXRpb25zLkl0IHRlc3RzIHRoZSBudWxsIGh5cG90aGVzaXMgdGhhdCB0aGVyZSBpcyBubyBkaWZmZXJlbmNlIGluIHN1cnZpdmFsIGJldHdlZW4gdGhlIGdyb3Vwcy4KClRoZSBvcHRpbWFsIGsgaXMgdGhlIG9uZSB3aXRoIHRoZSBtYXhpbXVtIGxvZ3Jhbmsgc3RhdGlzdGljIG9yIGF0IGFuIGluZmxlY3Rpb24gcG9pbnQgd2hlcmUgdGhlIHN0YXRpc3RpYyBwbGF0ZWF1cy4gCgoqKlNQV1NTKioKClNQV1NTIGlzIGNhbGN1bGF0ZWQgYXMgdGhlIHBvb2xlZCB3aXRoaW4tY2x1c3RlciBzdW0gb2Ygc3F1YXJlcyBhbmQgc3RhbmRhcmRpemVkIGJ5IHRoZSB0b3RhbCBzdW0gb2Ygc3F1YXJlcyBzaW1pbGFyIHRvIG1ldGhvZG9sb2d5IHVzZWQgaW4gdGhlIGdhcCBzdGF0aXN0aWMgW140XSB0byBzZWxlY3QgdGhlIGFwcHJvcHJpYXRlIG51bWJlciBvZiBjbHVzdGVycy4KClRoZSBvcHRpbWFsIG51bWJlciBvZiBjbHVzdGVycyBpcyB3aGVyZSBTUFdTUyBpcyBtaW5pbWl6ZWQgYW5kIGNyZWF0ZXMgYW4g4oCcZWxib3figJ0gb3IgYSBwb2ludCBvZiBpbmZsZWN0aW9uLCB3aGVyZSBhZGRpdGlvbiBvZiBtb3JlIGNsdXN0ZXJzIGRvZXMgbm90IGltcHJvdmUgY2x1c3RlciBzZXBhcmF0aW9uLiBTaW5jZSwgdGhpcyBtZXRyaWMgaXMgc3RhbmRhcmRpemVkIChsaWVzIGJldHdlZW4gMC0xKSwgaXQgY2FuIGFsc28gYmUgdXNlZCB0byBjb21wYXJlIGluZGl2aWR1YWwgcnVucyBvZiBkYXRhc2V0cyB0aGF0IGFyZSBtZWFzdXJlZCBvdmVyIHNhbWUgc2FtcGxlcy4KCioqQ2x1c3RlciBTaXplKioKClRoaXMgbWV0cmljIGNvbXB1dGVzIHRoZSBjbHVzdGVyIHNpemUgZm9yIGVhY2ggYGtgIGFjcm9zcyByb3VuZHMgb2YgY3Jvc3MtdmFsaWRhdGlvbi4gSXQgcmVjb3JkcyB0aGUgaW5zdGFuY2VzIHdoZXJlIGEgcm91bmQgb2YgY3Jvc3MgdmFsaWRhdGlvbiByZXR1cm5lZCBhIGNsdXN0ZXIgc2l6ZSB3aXRoIGxlc3MgdGhhbiA1IHN1YmplY3RzLiBTdWNoIGEgc29sdXRpb24gbWlnaHQgYmUgIHVucmVsaWFibGUgYW5kIHBlcmhhcHMgb3Zlci1maXR0ZWQgZm9yIHRoYXQgYGtgLiBBbiBvcHRpbWFsIGBrYCBzaG91bGQgaGF2ZSB0aGUgbGVhc3QgYW1vdW50IG9mIHN1Y2ggaW5zdGFuY2VzIGFjcm9zcyB0aGUgdG90YWwgY3Jvc3MgdmFsaWRhdGlvbiByb3VuZHMuICAKCjwvZGV0YWlscz4KCiMgRGF0YSBzY2VuYXJpb3MKQmVsb3cgd2Ugc2ltdWxhdGUgMyBkYXRhIHNjZW5hcmlvcyB0byBoaWdobGlnaHQgdGhlIGZ1bmN0aW9uYWxpdHkgb2Ygc3VydkNsdXN0LiBTdXJ2aXZhbCB0aW1lcyBmb3IgZGVtb25zdHJhdGlvbiBwdXJwb3NlcyB3aWxsIGFsc28gYmUgc2ltdWxhdGVkIHdoZXJlIHdlIGFzc3VtZSB0aGV5IGFyZSBkZXJpdmVkIGZyb20gYW4gZXhwb25lbnRpYWwgZGlzdHJpYnV0aW9uLiBBbHNvLCBub3RlIHRoYXQgaW4gYWRkaXRpb24gdG8gc2ltdWxhdGVkIGRhdGFzZXQgYmVpbmcgY29udGV4dCBmcmVlLCB3ZSBjYW4gYWxzbyBwcmVkZXRlcm1pbmUgYSBncm91bmQgdHJ1dGggdGhhdCBjb3VsZCBnaXZlIHVzIGEgZmFpciBlc3RpbWF0ZSBvZiBvdXIgbWV0aG9kJ3MgcGVyZm9ybWFuY2UuIAoKQSBoYW5keSBmdW5jdGlvbiB0byBzaW11bGF0ZSBpbnB1dCBkYXRhIGFuZCBzdXJ2aXZhbCBkYXRhIGlzIHByb3ZpZGVkIC0gYHNpbXVsYXRlX3N1cnZjbHVzdF9kYXRhX2Z1bi5SYC4gCgojIyBTY2VuYXJpbyAxCgpIZXJlIHdlIHdpbGwgc2ltdWxhdGUgYSBkYXRhc2V0IHdpdGggdGhyZWUgY2x1c3RlcnMsIGEgdG90YWwgb2YgMTUwICgkY18xPTUwLCBjXzI9NTAsIGNfMz01MCQpIHNhbXBsZXMgYW5kIDE1MCBmZWF0dXJlcyBzdWNoIHRoYXQ6CgokY18xIFxzaW0gTihcbXUgPSAtMS41LFxzaWdtYT0xKSQKCiRjXzIgXHNpbSBOKFxtdSA9IDAsIFxzaWdtYT0xKSQgYW5kCiAgICAgICAgCiRjXzMgXHNpbSBOKFxtdSA9IDEuNSwgXHNpZ21hPTEpJAoKTm90ZSB0aGF0LCBub3QgYWxsIDE1MCBmZWF0dXJlcyB3aWxsIGJlIGluZm9ybWF0aXZlIGFuZCB3ZSB3aWxsIG9ubHkgc2ltdWxhdGUgYWJvdXQgMTUgKDEwJSkgZmVhdHVyZXMgdGhhdCB3aWxsIHJlYWxseSBkcml2ZSB0aGUgZGlzdGluY3Rpb24gYmV0d2VlbiBjbHVzdGVycy4gVGhpcyBpcyBlc3BlY2lhbGx5IHRydWUgaW4gdGhlIGNvbnRleHQgb2YgaHVtYW4gZ2Vub21lIHdpdGggYWJvdXQgfjE5LDAwMCBnZW5lcywgd2hlcmUgb25seSBhIGZldyBvZiB0aGUgZ2VuZXMgd2lsbCBiZSBhY3R1YWxseSBhc3NvY2lhdGVkIHdpdGggdGhlIGRpc2Vhc2UgYmlvbG9neSBhbmQgc3Vydml2YWwuIAoKVGhlc2UgY2x1c3RlcnMgd2lsbCBhbHNvIGhhdmUgZGlzdGluY3QgbWVkaWFuIHN1cnZpdmFsIG9mICA0LjUsIDMuMjUsIGFuZCAyIHlycyByZXNwZWN0aXZlbHkuCgpNb3JlIHJlYWxpc3RpY2FsbHksIGZyb20gdGhlIGZlYXR1cmVzIHRoYXQgYXJlIGRpc3RpbmN0IHNvbWUgYXJlIGdvaW5nIHRvIGJlIGFzc29jaWF0ZWQgd2l0aCBzdXJ2aXZhbCBhbmQgd2hpbGUgb3RoZXJzIHJlbWFpbiBkaXN0aW5jdCB3aXRoIG5vIGFzc29jaWF0aW9uIHRvIHN1cnZpdmFsLiBXZSB3aWxsIHNpbXVsYXRlIGhhbGYgb2YgdGhlIGluZm9ybWF0aXZlIGZlYXR1cmVzIGFzIGRpc3RpbmN0IGFuZCBhc3NvY2lhdGVkIHdpdGggc3Vydml2YWwgYW5kIHJlbWFpbmluZyBoYWxmIGFzIGp1c3QgZGlzdGluY3QuIAoKYGBge3IsIGVjaG89RkFMU0UsIGZpZy5jYXA9ImZpZzpzY2hlbWF0aWNzIG9mIHNjZW5hcmlvMSJ9CmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKCJpbWFnZXMvc2NlbmFyaW8xLnBuZyIpCmBgYAoKCmBgYHtyfQojc2ltdWxhdGluZyBzY2VuYXJpbyAxCm1hdF9zY2VuYXJpbzEgPC0gbGlzdCgpCgojZml4aW5nIHRoZSBzb2x1dGlvbiB0byBzaW11bGF0ZWQgc2NlbmFyaW8xCnRydWVfc29sbl9zY2VuYXJpbzEgPC0gZGF0YS5mcmFtZShzYW1wbGVzID0gcGFzdGUwKCJTIiwxOjE1MCkgLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNsdXN0ZXIgPSBjKHJlcCgxLDUwKSwgcmVwKDIsNTApLCByZXAoMyw1MCkpKQoKI2Z1bmN0aW9uIGZyb20gc2ltdWxhdGVfc3VydmNsdXN0X2RhdGFfZnVuLlIgdG8gc2ltdWxhdGUgaW5wdXQgZGF0YQptYXRfc2NlbmFyaW8xW1sxXV0gPC0gc2ltdWxhdGVfZGF0YV90eXBlKGs9MywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAga19zaXplID0gcmVwKDUwLDMpLCBuX2ZlYXR1cmVzID0gMTUwLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG11ID0gYygtMS41LCAwLCAxLjUpLCBzZCA9IHJlcCgxLDMpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBlcmNfaW5mb3JtID0gMC4xMCwgbXlfc2VlZCA9IDExMiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwZXJtdXRlID0gVFJVRSkKI3NpbXVsYXRpbmcgc3Vydml2YWwgZGF0YSAKc3Vydl9zY2VuYXJpbzEgPC0gc2ltdWxhdGVfc3Vydl9kYXQoaz0zLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAga19zaXplID0gcmVwKDUwLDMpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtZWRfc3Vydl90aW1lcyA9IGMoNC41LCAzLjI1LCAyKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWF4X3N1cnZpdmFsID0gMTAsIG15X3NlZWQgPSAxMTIpCgpgYGAKCkJlbG93IHdlIHBsb3QgdGhlIGRpc3RyaWJ1dGlvbiBvZiBvdXIgc2ltdWxhdGVkIDMtY2xhc3MgY2x1c3RlcnMgYW5kIHRoZSBzdXJ2aXZhbCBjdXJ2ZXMgaW4gYSBLYXBsYW4gTWVpZXIgcGxvdC4gCgpgYGB7ciwgbWVzc2FnZT1GQUxTRX0KCiNsb25nIGZvcm1hdCB0byB3cmFuZ2xlIGR0YSB0byBwbG90IGdncmlkZ2VzIGFuZCBqb2luIHRoZSB0cnV0aCAKbWF0X3NjZW5hcmlvMV9sb25nIDwtIG1hdF9zY2VuYXJpbzFbWzFdXSAlPiUKICAgIGFzLmRhdGEuZnJhbWUoKSAlPiUKICAgIHRpYmJsZTo6cm93bmFtZXNfdG9fY29sdW1uKHZhciA9ICJzYW1wbGVzIikgJT4lCiAgICB0aWR5cjo6cGl2b3RfbG9uZ2VyKC4sIGNvbHMgPSAtc2FtcGxlcywgbmFtZXNfdG8gPSAiZmVhdHVyZXMiKSAlPiUKICAgIGxlZnRfam9pbiguLCB0cnVlX3NvbG5fc2NlbmFyaW8xLCBieSA9InNhbXBsZXMiKSAlPiUKICAgIG11dGF0ZShjbHVzdGVyID0gYXMuZmFjdG9yKGNsdXN0ZXIpKQoKI2lucHV0IGRhdGEgcGxvdApwMSA9IGdncGxvdChtYXRfc2NlbmFyaW8xX2xvbmcsIGFlcyh4ID0gdmFsdWUsIHkgPSBjbHVzdGVyKSkgKyBnZW9tX2RlbnNpdHlfcmlkZ2VzKGFlcyhmaWxsID0gY2x1c3RlciksIHNjYWxlID0gMiwgYWxwaGEgPSAwLjYpICsgdGhlbWVfYncoKSArIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IG15X2NvbF9wYWxbMTozXSkgKyBnZ3RpdGxlKCJjbHVzdGVyIGRpc3RyaWJ1dGlvbiBvZiBrPTMgY2x1c3RlcnMsIHNjZW5hcmlvMSIpCgojS00gcGxvdApwMiA9IHN1cnZfc2NlbmFyaW8xICU+JQogICAgdGliYmxlOjpyb3duYW1lc190b19jb2x1bW4odmFyID0gInNhbXBsZXMiKSAlPiUKICAgIGxlZnRfam9pbiguLCB0cnVlX3NvbG5fc2NlbmFyaW8xLCBieSA9InNhbXBsZXMiKSAlPiUKICAgIGdnc3VydnBsb3Qoc3VydmZpdChTdXJ2KHRpbWUsIGV2ZW50KSB+IGNsdXN0ZXIsIGRhdGEgPSAuKSwgZGF0YSA9IC4sIAogICAgICAgICAgICAgICBzdXJ2Lm1lZGlhbi5saW5lID0gInYiLCBwYWxldHRlID0gbXlfY29sX3BhbCkgKyAKICAgIGdndGl0bGUoInN1cnZpdmFsIGRpc3RyaWJ1dGlvbiBhY3Jvc3MgXG4gaz0zIGNsdXN0ZXJzLCBzY2VuYXJpbzEiKQoKCmBgYAoKYGBge3IsIGZpZy53aWR0aD0xMH0KZ2dhcnJhbmdlKHAxLCAocDIkcGxvdCArIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEzKSkpLCBucm93PTEpCgpgYGAKCldlIHNlZSB0aGF0IHNpbmNlIG9ubHkgMTAlIG9mIHRoZSBmZWF0dXJlcyBhcmUgaW5mb3JtYXRpdmUgYW5kIDkwJSBhcmUgbm9pc2UsIHRoZSBvdmVyYWxsIGRpc3RyaWJ1dGlvbiBhcHBlYXJzIHRvIGJlIHVuaWZvcm0gZm9yIHRoZSB0aHJlZSBjbHVzdGVycy4gCgpUaGUgZGFzaGVkIHZlcnRpY2FsIGxpbmUgZnJvbSBlYWNoIHN1cnZpdmFsIGN1cnZlIHJlcHJlc2VudHMgdGhlIG1lZGlhbiBzdXJ2aXZhbCByYXRlIGZvciBlYWNoIGdyb3VwLiBTZWUgYmVsb3cgdGhlIGFjdHVhbCBmaXQgZnJvbSBgc3VydmZpdGAsIGFzIGNvbXBhcmVkIHdoYXQgd2UgYWN0dWFsbHkgdXNlZCBpbiBvdXQgc2ltdWxhdGlvbiwgJFxsYW1iZGFfe2MxfSA9IDQuNSwgXGxhbWJkYV97YzJ9PTMuMjUsIFxsYW1iZGFfe2MzfSA9IDIkIAoKYGBge3J9CiNzdXJ2Zml0IHRvIGNvbXB1dGUgbWVkaWFuIHN1cnZpdmFsIHRpbWVzCmZpdCA8LSBzdXJ2X3NjZW5hcmlvMSAlPiUKICAgIHRpYmJsZTo6cm93bmFtZXNfdG9fY29sdW1uKHZhciA9ICJzYW1wbGVzIikgJT4lCiAgICBsZWZ0X2pvaW4oLiwgdHJ1ZV9zb2xuX3NjZW5hcmlvMSwgYnkgPSJzYW1wbGVzIikgJT4lCiAgICBzdXJ2Zml0KFN1cnYodGltZSwgZXZlbnQpIH4gY2x1c3RlciwgZGF0YSA9IC4pCgpmaXRfbWVkaWFuIDwtIHN1bW1hcnkoZml0KSR0YWJsZVssJ21lZGlhbiddIAoKZGF0YS5mcmFtZShjbHVzdGVyID0gZ3N1YigiY2x1c3Rlcj0iLCJjIixuYW1lcyhmaXRfbWVkaWFuKSksIAogICAgICAgICAgIGBtZWRpYW4gc3Vydml2YWwoeXJzKWAgPSByb3VuZChmaXRfbWVkaWFuLDIpKSAlPiUgCiAgZ3Q6Omd0KGNhcHRpb24gPSAibWVkaWFuIHN1cnZpdmFsIHRpbWVzIGFyZSBmcm9tIGBzdXJ2Zml0YCBmaXQiKQoKYGBgCgoKIyMjIHN1cnZDbHVzdCBydW4gCgpPbmNlIHRoZSBzaW11bGF0ZWQgZGF0YSBpcyBhc3NlbWJsZWQsIHdlIHByb2NlZWQgdG8gcnVubmluZyBzdXJ2Q2x1c3QgdmlhIGBjdl9jdXJ2Y2x1c3RgLiBFYWNoIGBrYCB3aWxsIGJlIGNyb3NzLXZhbGlkYXRlZCBhY3Jvc3MgMy1mb2xkcyBmb3IgMTAgcm91bmRzIHdoZXJlIGZyZXNoIHJlc2FtcGxpbmcgZm9yIGZvbGRzIGlzIGRvbmUgYXQgZXZlcnkgcm91bmQuIENvbXB1dGF0aW9uIGFjcm9zcyBlYWNoIGBrYCAod2hlcmUgayA9IDIgdG8gNyBjbHVzdGVycykgd2lsbCBiZSBwYXJhbGxlbGl6ZWQgdXNpbmcgYGRvcGFyYCBmcm9tIHBhY2thZ2UgYGRvUGFyYWxsZWxgLiAKClRoaXMgaXMgd3JhcHBlZCB1cCBpbiBhIGZ1bmN0aW9uIGJ5IHRoZSBuYW1lIG9mIGBzaW1fY3Zyb3VuZHNgLCBFeHBhbmQgdGhlIGNvZGUgc2VjdGlvbiBiZWxvdy4gCgpgYGB7ciwgZWNobz1UUlVFLCBjb2xsYXBzZT1GQUxTRSwgcmVzdWx0cz0nbWFya3VwJ30Kc2ltX2N2cm91bmRzPC1mdW5jdGlvbihraywgc2ltZGF0LCBzaW1zdXJ2ZGF0LCBmb2xkPTMsIGN2X3JvdW5kcz0xMCl7CiAgICB0aGlzLmZvbGQ8LWZvbGQKICAgIGZpdDwtbGlzdCgpCiAgICBmb3IgKGkgaW4gc2VxX2xlbihjdl9yb3VuZHMpKXsKICAgICAgICBmaXRbW2ldXSA8LSBzdXJ2Q2x1c3Q6OmN2X3N1cnZjbHVzdChzaW1kYXQsIHNpbXN1cnZkYXQsa2ssdGhpcy5mb2xkKQogICAgfQogICAgcmV0dXJuKGZpdCkKfSAKYGBgCgpgYGB7cn0KcmVnaXN0ZXJEb1BhcmFsbGVsKGNsIDwtIG1ha2VDbHVzdGVyKDYpKQoKcHRtIDwtIFN5cy50aW1lKCkKCiNlYWNoIGNvcmUgcGVyZm9ybXMgY29tcHV0YXRpb24gZm9yIGVhY2ggayBjbHVzdGVyCnNpbTFfY3ZfZml0IDwtIGZvcmVhY2goa2s9Mjo3KSAlZG9wYXIlIHNpbV9jdnJvdW5kcyhraywgc2ltZGF0ID0gbWF0X3NjZW5hcmlvMSwgc2ltc3VydmRhdCA9IHN1cnZfc2NlbmFyaW8xKQoKcHRtMiA8LSBTeXMudGltZSgpCnN0b3BDbHVzdGVyKGNsKQoKdHQgPSBwdG0yLXB0bQpgYGAKClRoZSBjcm9zcyB2YWxpZGF0aW9uIHRvb2sgYHIgcm91bmQoYXMuZG91YmxlKHR0LCB1bml0cyA9ICJtaW5zIiksMilgIG1pbnV0ZXMgdG8gcnVuLiAKCiMjIyBSZXN1bHRzIGFuZCBjaG9vc2luZyBrCgpBbGwgY3Jvc3MgdmFsaWRhdGVkIHJvdW5kcyBvZiBkZXNpcmVkIGsgY2x1c3RlcnMgaXMgdGhlbiBldmFsdWF0ZWQgYWNyb3NzIDMgbWV0cmljcyAtIAoKICogTG9ncmFuayB0ZXN0IHN0YXRpc3RpYwogKiBTdGFuZGFyZGl6ZWQgcG9vbGVkIHdpdGhpbiBzdW0gb2Ygc3F1YXJlcyAoU1BXU1MpCiAqIE51bWJlciBvZiByZWxpYWJsZSBzb2x1dGlvbnMgCgpNb3JlIGRldGFpbHMgb24gdGhlIG1ldHJpY3MgY2FuIGJlIGZvdW5kIFtoZXJlXSgjd2Zsb3cpICAKCkluIHRoZSBwbG90IGJlbG93LCB0aGUgdG9wLWxlZnQgcGxvdCBpcyBzdW1tYXJpemluZyAqKmxvZ3JhbmsgdGVzdCBzdGF0aXN0aWMqKiBvdmVyIDEwIHJvdW5kcyBvZiBjcm9zcy12YWxpZGF0ZWQgY2xhc3MgbGFiZWxzIGFjcm9zcyAzLWZvbGQgY3Jvc3MtdmFsaWRhdGlvbi4gRWFjaCBkb3QgaXMgYSBzb2x1dGlvbiBmcm9tIGEgY3Jvc3MgdmFsaWRhdGVkIHJvdW5kIGZvciB0aGF0IGBrYC4gSGVyZSwgd2Ugc2VlIHRoYXQgbG9ncmFuayBwZWFrcyBhdCBgaz0zYC4gCgpUaGUgdG9wLXJpZ2h0IHBsb3QgaXMgc3VtbWFyaXppbmcgU1BXU1Mgb3IgKipTdGFuZGFyZGl6ZWQgUG9vbGVkIFdpdGhpbiBTdW0gb2YgU3F1YXJlcyoqLiBJdCBhcHBlYXJzIHRoYXQgU1BXU1MgZGVjcmVhc2VzIG1vbm90b25pY2FsbHkgYXMgdGhlIG51bWJlciBvZiBjbHVzdGVycyBga2AgaW5jcmVhc2VzLiBUaGUgb3B0aW1hbCBudW1iZXIgb2YgY2x1c3RlcnMgaXMgd2hlcmUgU1BXU1MgY3JlYXRlcyBhbiDigJxlbGJvd+KAnSAsIGhlcmUgdGhlIHBsb3QgZWxib3dzIGF0IGBrPTNgCgpUaGUgbGFzdCBwbG90LCBvbiB0aGUgYm90dG9tLWxlZnQsIHNob3dzIGZvciBlYWNoIGBrYCBob3cgbWFueSBga2AgY2xhc3MgbGFiZWxzIGhhdmUgYDw9NWAgc2FtcGxlcyBpbiAxMCByb3VuZHMgb2YgY3Jvc3MgdmFsaWRhdGlvbi4gSW4gb3VyIGNhc2UgaGVyZSwgZm9yIGBrPTNgIHRoZSBudW1iZXIgb2YgY2xhc3NlcyB3aXRoIGA8PTVgIHNhbXBsZXMgaXMgemVyby4gCgpgYGB7cn0Kc3Nfc3RhdHMgPC0gZ2V0U3RhdHMoc2ltMV9jdl9maXQsIGtrPTcsIGN2cj0xMCkKCnBfbHIgPC0gcGxvdFN0YXRzX3RpZHkoc3Nfc3RhdHMkbHIsIGhpZ2hsaWdodF9rID0gMykKcF9sciA8LSBwX2xyICsgeWxhYigibG9ncmFuayB0ZXN0IHN0YXRpc3RpYyIpCgpwX3Nwd3NzIDwtIHBsb3RTdGF0c190aWR5KHNzX3N0YXRzJHNwd3NzLCBoaWdobGlnaHRfayA9IDMpCnBfc3B3c3MgPC0gcF9zcHdzcyArIHlsYWIoIlNQV1NTIikKCnBfY2x1c2l6ZSA8LSBkYXRhLmZyYW1lKGsgPSBwYXN0ZTAoImsiLDI6NyksIGN2X2xlc3NfdGhhbl81ID0gc3Nfc3RhdHMkYmFkLnNvbCkgJT4lCiAgICBnZ3Bsb3QoYWVzKHg9aywgeSA9IGN2X2xlc3NfdGhhbl81KSkgKyAKICAgIGdlb21fbGluZShjb2wgPSAic2t5Ymx1ZSIsIGdyb3VwID0gImsiKSArIGdlb21fcG9pbnQoKSArIAogICAgdGhlbWVfbWluaW1hbCgpICsgeWxhYigibm8uIG9mIGN2IHNvbHV0aW9uIHdpdGggPD01IHNhbXBsZXMiKQoKYGBgCgoKYGBge3IsIGZpZy53aWR0aD04LCBmaWcuaGVpZ2h0PTh9CmdnYXJyYW5nZShwX2xyLCBwX3Nwd3NzLCBwX2NsdXNpemUsIG5yb3c9MiwgbmNvbD0yKQpgYGAKCgpgYGB7ciwgbWVzc2FnZT1GQUxTRX0KI2N2X3ZvdGluZyBjcmVhdGVzIGEgY29uc2Vuc3VzIG9mIGFsbCBzb2x1dGlvbnMgYWNyb3NzIHRoZSAxMCBjcm9zcyB2YWxpZGF0ZWQgcm91bmRzIAprMyA8LSBjdl92b3RpbmcoY3YuZml0ID0gc2ltMV9jdl9maXQsCiAgICAgICAgICAgICAgICBkYXQuZGlzdCA9IGdldERpc3QoZGF0YXNldHMgPSBtYXRfc2NlbmFyaW8xLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN1cnZkYXQgPSBzdXJ2X3NjZW5hcmlvMSksIAogICAgICAgICAgICAgICAgcGlja19rPTMpCmBgYAoKIyMjIyBrPTMKCkZyb20gdGhlIG1ldHJpY3MgYWJvdmUsIHdlIGNob29zZSBgaz0zYCBhcyBvdXIgb3B0aW1hbCBrLiBXZSBjYW4gY29tcGFyZSB0aGlzIHdpdGggb3VyIHNpbXVsYXRlZCB0cnVlIHNvbHV0aW9uIC0gCgpgYGB7cn0KI2NyZWF0aW5nIHRhYiB0byBzdG9yZSByZXN1bHRzIAp0YWIgPC0gdHJ1ZV9zb2xuX3NjZW5hcmlvMSAlPiUgCiAgICBsZWZ0X2pvaW4oLiwgZGF0YS5mcmFtZShzaW1fc29sbiA9IGszLCBzYW1wbGVzID0gcGFzdGUwKCJTIiwgMToxNTApKSwgCiAgICAgICAgICAgICAgYnkgPSAic2FtcGxlcyIpCgp0YWIgJT4lIAogICAgZHBseXI6OnNlbGVjdCgtc2FtcGxlcykgJT4lIAogICAgZHBseXI6OnJlbmFtZSgidHJ1dGgiID0gY2x1c3RlcikgJT4lCiAgICBndHN1bW1hcnk6OnRibF9zdW1tYXJ5KGJ5ID0gc2ltX3NvbG4pICU+JSBtb2RpZnlfaGVhZGVyKGxhYmVsID0gIioqc2ltdWxhdGVkIHNvbHV0aW9uKioiKQoKYXJpIDwtIHJvdW5kKG1jbHVzdDo6YWRqdXN0ZWRSYW5kSW5kZXgodGFiJHNpbV9zb2xuLCB0YWIkY2x1c3RlciksMikKYGBgCgpUaGUgY29uY29yZGFuY2UgYmV0d2VlbiB0cnVlIGFuZCBzdXJ2Q2x1c3QgcHJlZGljdGVkIHNvbHV0aW9uIGNhbiBiZSBjb21wdXRlZCBieSBSYW5kIEluZGV4LiAKClRoZSBSYW5kIEluZGV4IGlzIHVzZWQgdG8gbWVhc3VyZSBhZ3JlZW1lbnQgYmV0d2VlbiB0d28gY2xhc3NpZmljYXRpb24gbGFiZWxzLiBJdCBoYXMgYSBtYXhpbXVtIG9mIDEgYW5kIGEgbWluaW11bSBvZiAwLiBIZXJlIDAgbWVhbnMgdGhlIHR3byBkYXRhIGxhYmVscyBoYXZlIG5vIHNoYXJlZCBpbmZvcm1hdGlvbiBhbmQgMSBtZWFucyB0aGV5IGFyZSB0aGUgc2FtZSBsYWJlbHMuCgojIyMgQ29uY2x1c2lvbiAKClRoZSBjb25jb3JkYW5jZSBiZXR3ZWVuIHRoZSBzdXJ2Q2x1c3QgcmVwb3J0ZWQgc29sdXRpb25zIGFuZCB0cnV0aCBjbGFzcyBsYWJlbHMgaXMgaGlnaCBgciBhcmlgLCBtZWFuaW5nIHN1cnZDbHVzdCBkaWQgYSBnb29kIGpvYiBpbiByZWNvdmVyaW5nIHRoZSB0cnVlIHNvbHV0aW9uIHdoZW4gZmVhdHVyZXMgY2FycnkgYSBtaXhlZCBzaWduYWwuIEFsc28sIG5vdGUgdGhhdCwgZXZlbiB0aG91Z2ggb25seSAxMCUgb2YgdGhlIGZlYXR1cmVzIHdlcmUgaW5mb3JtYXRpdmUsIHdlIHNwbGl0IHRoaXMgaW4gaGFsZiB0byBzaW11bGF0ZSBmZWF0dXJlcyB0aGF0IHdlcmUgZGlzdGluY3QgYW5kIGFzc29jaWF0ZWQgd2l0aCBzdXJ2aXZhbCBhbmQgc3VydkNsdXN0IHdhcyBhYmxlIHRvIGJvcnJvdyBmcm9tIHRoZSA1JSBvZiB0aGUgaW5mb3JtYXRpdmUgZmVhdHVyZXMuIAoKCiMjIFNjZW5hcmlvIDIKCnN1cnZDbHVzdCBjYW4gcGVyZm9ybSBjbHVzdGVyaW5nIG9uIG1vcmUgdGhhbiBvbmUgZGF0YSB0eXBlIGFuZCBjYW4gb3V0cHV0IHJlc3VsdHMgYnkgaW50ZWdyYXRpbmcgdGhlIGRhdGFzZXRzLiBJbiBnZW5vbWljIGNvbnRleHQsIHRoZXNlIGRpZmZlcmVudCBkYXRhdHlwZXMgYXJlIHRoZSBkaWZmZXJlbnQgYmlvbG9naWNhbCBhdmVudWVzIHN1Y2ggYXMgZ2VuZSBleHByZXNzaW9uIGRhdGEgKGNvbnRpbnVvdXMpLCBnZW5lIG11dGF0aW9uIHN0YXR1cyAoYmluYXJ5KSBldGMuIGNhcHR1cmluZyBkaWZmZXJlbnQgbGF5ZXJzIG9mIHRoZSB0dW1vciBiaW9sb2d5LiAKCkhlcmUgd2Ugd2lsbCBzaW11bGF0ZSB0d28gZGF0YXNldCB3aXRoIHRocmVlIGNsdXN0ZXJzLCBhIHRvdGFsIG9mIDE1MCBzYW1wbGVzIGFuZCAxMDAgZmVhdHVyZXMgd2l0aCBvbmUgZGF0YXNldCBoYXZpbmcgc3Ryb25nIGNsdXN0ZXJpbmcgaW5mb3JtYXRpb24gYW5kIHRoZSBvdGhlciBoYXZpbmcgd2VhayBjbHVzdGVyaW5nIGluZm9ybWF0aW9uLiBTZWUgYmVsb3cgLSAKCipEYXRhc2V0IDEqCgpTaW11bGF0ZSB0aHJlZSBjbHVzdGVycyAoJGNfMT01MCwgY18yPTUwLCBjXzM9NTAkKSBzdWNoIHRoYXQKCiRjXzEgXHNpbSBOKFxtdSA9IC0xLjUsXHNpZ21hPTEpJCwKICAgICAgICAKJGNfMiBcc2ltIE4oXG11ID0gMCwgXHNpZ21hPTEpJCBhbmQKCiRjXzMgXHNpbSBOKFxtdSA9IDEuNSwgXHNpZ21hPTEpJAoKKkRhdGFzZXQgMioKClNpbXVsYXRlIHRocmVlIGNsdXN0ZXJzICgkY18xPTUwLCBjXzI9NTAsIGNfMz01MCQpIHN1Y2ggdGhhdCAtIAoKJGNfMSBcc2ltIE4oXG11ID0gLTAuNSxcc2lnbWE9MSkkLAogICAgICAgIAokY18yIFxzaW0gTihcbXUgPSAwLCBcc2lnbWE9MSkkIGFuZAogICAgICAgIAokY18zIFxzaW0gTihcbXUgPSAwLjUsIFxzaWdtYT0xKSQKICAgICAgICAKRGF0YXNldDIgY2FuIGJlIGludGVycHJldGVkIGFzIGEgbW9kYWxpdHkgdGhhdCBpcyBhZGRpbmcgbm9pc2UgdG8gdGhlIG1vZGFsaXR5IG9mIHRoZSBkYXRhIHRoYXQgZXhwbGFpbnMgdGhlIHRydWUgc3Vydml2YWwgYXNzb2NpYXRpb24uIEZvciB0aGlzIHNjZW5hcmlvLCB0aGUgc3Vydml2YWwgZGF0YSByZW1haW5zIHNhbWUgYXMgc2NlbmFyaW8gMS4gCgpgYGB7cn0KCiNzY2VuYXJpbyAyIHNldCB1cAptYXRfc2NlbmFyaW8yIDwtIGxpc3QoKQoKdHJ1ZV9zb2xuX3NjZW5hcmlvMiA8LSB0cnVlX3NvbG5fc2NlbmFyaW8xCgojc2ltdWxhdGUgZGF0YSBzZXQgYnV0IHdpdGggc3Ryb25nIGNsdXN0ZXIgYXNzb2NpYXRpb24gCm1hdF9zY2VuYXJpbzJbWzFdXSA8LSBzaW11bGF0ZV9kYXRhX3R5cGUoaz0zLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBrX3NpemUgPSByZXAoNTAsMyksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5fZmVhdHVyZXMgPSAxMDAsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG11ID0gYygtMS41LCAwLCAxLjUpLCBzZCA9IHJlcCgxLDMpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBlcmNfaW5mb3JtID0gMC4xMCwgbXlfc2VlZCA9IDEyMykKCgojc2ltdWxhdGUgYW4gYWRkaXRpb25hbCBkYXRhIHNldCBidXQgd2l0aCB3ZWFrIGNsdXN0ZXIgYXNzb2NpYXRpb24gCm1hdF9zY2VuYXJpbzJbWzJdXSA8LSBzaW11bGF0ZV9kYXRhX3R5cGUoaz0zLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBrX3NpemUgPSByZXAoNTAsMyksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5fZmVhdHVyZXMgPSAxNTAsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG11ID0gYygtMC41LCAwLCAwLjUpLCBzZCA9IHJlcCgxLDMpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBlcmNfaW5mb3JtID0gMC4xMCwgbXlfc2VlZCA9IDEyMykKCnN1cnZfc2NlbmFyaW8yIDwtIHN1cnZfc2NlbmFyaW8xCgpgYGAKCkJlbG93LCB3ZSBwbG90IHRoZSBkaXN0cmlidXRpb24gb2Ygb25seSBpbmZvcm1hdGl2ZSBmZWF0dXJlcyBhY3Jvc3MgY2x1c3RlcnMgYWNyb3NzIHRoZSB0d28gZGF0YXNldHMuIFRoZSBkYXNoZWQgZ3JleSBsaW5lcyByZXByZXNlbnQgdGhlIG1lYW5zIGFzIHdlIHNpbXVsYXRlZCBhY3Jvc3MgdGhlIHR3byBkYXRhc2V0cy4gCgpgYGB7ciwgZmlnLndpZHRoPTh9CgojbG9uZyBmb3JtIG9mIGRhdGFzZXQgdG8gdmlzdWFsaXplIGNsdXN0ZXIgZGlzdHJpYnV0aW9uIAojZGF0YXNldDEKbWF0X3NjZW5hcmlvMl9kYXQxX2xvbmcgPC0gbWF0X3NjZW5hcmlvMltbMV1dICU+JQogICAgYXMuZGF0YS5mcmFtZSgpICU+JQogICAgdGliYmxlOjpyb3duYW1lc190b19jb2x1bW4odmFyID0gInNhbXBsZXMiKSAlPiUKICAgIHRpZHlyOjpwaXZvdF9sb25nZXIoLiwgY29scyA9IC1zYW1wbGVzLCBuYW1lc190byA9ICJmZWF0dXJlcyIpICU+JQogICAgbGVmdF9qb2luKC4sIHRydWVfc29sbl9zY2VuYXJpbzIsIGJ5ID0ic2FtcGxlcyIpICU+JQogICAgbXV0YXRlKGNsdXN0ZXIgPSBhcy5mYWN0b3IoY2x1c3RlcikpCgpwMSA8LSBnZ3Bsb3QobWF0X3NjZW5hcmlvMl9kYXQxX2xvbmcgJT4lIAogICAgICAgICAgICAgICAgZmlsdGVyKGZlYXR1cmVzICVpbiUgcGFzdGUwKCJGIiwxOjEwICkpLCBhZXMoeCA9IHZhbHVlLCB5ID0gY2x1c3RlcikpICsgCiAgICBnZW9tX2RlbnNpdHlfcmlkZ2VzKGFlcyhmaWxsID0gY2x1c3RlciksIHNjYWxlID0gMiwgYWxwaGEgPSAwLjYpICsgdGhlbWVfYncoKSArIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IG15X2NvbF9wYWxbMTozXSkgKyAKICAgIGdndGl0bGUoImRpc3RyaWJ1dGlvbiBvZiBpbmZvcm1hdGl2ZSBmZWF0dXJlcyBvbmx5LCBkYXRhc2V0MSwgc2NlbmFyaW8yIikgCgpwMSA8LSBwMSArCiAgICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemU9MTApKSArIAogICAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gYygtMS41LDAsMS41KSwgY29sID0gImdyZXkiLCBsaW5ldHlwZSA9ICJkYXNoZWQiKQoKI2RhdGFzZXQyCm1hdF9zY2VuYXJpbzJfZGF0Ml9sb25nIDwtIG1hdF9zY2VuYXJpbzJbWzJdXSAlPiUKICAgIGFzLmRhdGEuZnJhbWUoKSAlPiUKICAgIHRpYmJsZTo6cm93bmFtZXNfdG9fY29sdW1uKHZhciA9ICJzYW1wbGVzIikgJT4lCiAgICB0aWR5cjo6cGl2b3RfbG9uZ2VyKC4sIGNvbHMgPSAtc2FtcGxlcywgbmFtZXNfdG8gPSAiZmVhdHVyZXMiKSAlPiUKICAgIGxlZnRfam9pbiguLCB0cnVlX3NvbG5fc2NlbmFyaW8yLCBieSA9InNhbXBsZXMiKSAlPiUKICAgIG11dGF0ZShjbHVzdGVyID0gYXMuZmFjdG9yKGNsdXN0ZXIpKQoKcDIgPC0gZ2dwbG90KG1hdF9zY2VuYXJpbzJfZGF0Ml9sb25nICU+JSAKICAgICAgICAgICAgICAgIGZpbHRlcihmZWF0dXJlcyAlaW4lIHBhc3RlMCgiRiIsMToxMCApKSwgYWVzKHggPSB2YWx1ZSwgeSA9IGNsdXN0ZXIpKSArIAogICAgZ2VvbV9kZW5zaXR5X3JpZGdlcyhhZXMoZmlsbCA9IGNsdXN0ZXIpLCBzY2FsZSA9IDIsIGFscGhhID0gMC42KSArIHRoZW1lX2J3KCkgKyBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBteV9jb2xfcGFsWzE6M10pICsgZ2d0aXRsZSgiZGlzdHJpYnV0aW9uIG9mIGluZm9ybWF0aXZlIGZlYXR1cmVzIG9ubHksIGRhdGFzZXQyLCBzY2VuYXJpbzIiKQoKcDIgPC0gcDIgKyAKICAgIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZT0xMCkpICsKICAgIGdlb21fdmxpbmUoeGludGVyY2VwdCA9IGMoLTAuNSwwLDAuNSksIGNvbCA9ICJncmV5IiwgbGluZXR5cGUgPSAiZGFzaGVkIikKCmdnYXJyYW5nZShwMSwgcDIsIG5yb3c9MSkKYGBgCgojIyMgc3VydkNsdXN0IHJ1biAKCkluIHRoaXMgc2VjdGlvbiB3ZSB3aWxsIGJlIHBlcmZvcm1pbmcgd2VpZ2h0ZWQgaW50ZWdyYXRpdmUgY2x1c3RlcmluZyBzdXJ2Q2x1c3QgdmlhIHByZXZpb3VzbHkgZGVmaW5lZCBmdW5jdGlvbiBgc2ltX2N2cm91bmRzYC4KCmBgYHtyfQpyZWdpc3RlckRvUGFyYWxsZWwoY2wgPC0gbWFrZUNsdXN0ZXIoNikpCgpwdG0gPC0gU3lzLnRpbWUoKQpzaW0yX2N2X2ZpdCA8LSBmb3JlYWNoKGtrPTI6NykgJWRvcGFyJSBzaW1fY3Zyb3VuZHMoa2ssIHNpbWRhdCA9IG1hdF9zY2VuYXJpbzIsIHNpbXN1cnZkYXQgPSBzdXJ2X3NjZW5hcmlvMikKcHRtMiA8LSBTeXMudGltZSgpCgpzdG9wQ2x1c3RlcihjbCkKCnR0ID0gcHRtMi1wdG0KYGBgCgpUaGUgY3Jvc3MgdmFsaWRhdGlvbiB0b29rIGByIHJvdW5kKGFzLmRvdWJsZSh0dCwgdW5pdHM9Im1pbnMiKSwyKWAgbWludXRlcyB0byBydW4uIAoKIyMjIFJlc3VsdHMgYW5kIGNob29zaW5nIGsKCkluIHRoZSBwbG90IGJlbG93LCB0aGUgdG9wLWxlZnQgcGxvdCBzdW1tYXJpemluZyBsb2dyYW5rIHRlc3Qgc3RhdGlzdGljIG92ZXIgMTAgcm91bmRzIG9mIGNyb3NzLXZhbGlkYXRlZCBmb3IgZWFjaCBrLCB3ZSBzZWUgaXQgaXMgbWF4aW11bSBmb3IgYGs9M2AuIFRoaXMgaXMgaW4gY29uY29yZGFuY2Ugd2l0aCBTUFdTUywgd2hlcmUgdGhlIHBvaW50IG9mIGluZmxlY3Rpb24gaXMgYWxzbyBhdCBgaz0zYC4gCgpgYGB7cn0KCiNjb2RlIHRvIHNlZSBldmFsdWF0ZSBzdXJ2Q2x1c3QgcnVuIGFjcm9zcyBsb2dyYW5rIHRlc3Qgc3RhdHMsIHNwd3NzCgpzc19zdGF0cyA8LSBnZXRTdGF0cyhzaW0yX2N2X2ZpdCwga2s9NywgY3ZyPTEwKQoKcF9sciA8LSBwbG90U3RhdHNfdGlkeShzc19zdGF0cyRsciwgaGlnaGxpZ2h0X2sgPSAzKQpwX2xyIDwtIHBfbHIgKyB5bGFiKCJsb2dyYW5rIHRlc3Qgc3RhdGlzdGljIikKCnBfc3B3c3MgPC0gcGxvdFN0YXRzX3RpZHkoc3Nfc3RhdHMkc3B3c3MsIGhpZ2hsaWdodF9rID0gMykKcF9zcHdzcyA8LSBwX3Nwd3NzICsgeWxhYigiU1BXU1MiKQoKcF9jbHVzaXplIDwtIGRhdGEuZnJhbWUoayA9IHBhc3RlMCgiayIsMjo3KSwgY3ZfbGVzc190aGFuXzUgPSBzc19zdGF0cyRiYWQuc29sKSAlPiUKICAgIGdncGxvdChhZXMoeD1rLCB5ID0gY3ZfbGVzc190aGFuXzUpKSArIGdlb21fcG9pbnQoKSArIGdlb21fbGluZShjb2wgPSAic2t5Ymx1ZSIsIGdyb3VwPSJrIikgKyB0aGVtZV9taW5pbWFsKCkgKyB5bGFiKCJjdiBzb2x1dGlvbiB3aXRoIDw9NSBzYW1wbGVzIikKYGBgCgoKYGBge3IsIGZpZy53aWR0aD04LCBmaWcuaGVpZ2h0PTh9CmdnYXJyYW5nZShwX2xyLCBwX3Nwd3NzLCBwX2NsdXNpemUsIG5yb3c9MiwgbmNvbD0yKQpgYGAKCgpgYGB7ciwgbWVzc2FnZT1GQUxTRX0KazMgPC0gY3Zfdm90aW5nKGN2LmZpdCA9IHNpbTJfY3ZfZml0LAogICAgICAgICAgICAgICAgZGF0LmRpc3QgPSBnZXREaXN0KGRhdGFzZXRzID0gbWF0X3NjZW5hcmlvMiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdXJ2ZGF0ID0gc3Vydl9zY2VuYXJpbzIpLCAKICAgICAgICAgICAgICAgIHBpY2tfaz0zKQpgYGAKCiMjIyMgaz0zCgpGcm9tIHRoZSBtZXRyaWNzIGFib3ZlLCB3ZSBjaG9vc2UgYGs9M2AgYXMgb3VyIG9wdGltYWwgay4gV2UgY2FuIGNvbXBhcmUgdGhpcyB3aXRoIG91ciBzaW11bGF0ZWQgdHJ1ZSBzb2x1dGlvbiAtIAoKYGBge3J9Cgp0YWIgPC0gdHJ1ZV9zb2xuX3NjZW5hcmlvMiAlPiUgCiAgICBsZWZ0X2pvaW4oLiwgZGF0YS5mcmFtZShzaW1fc29sbiA9IGszLCBzYW1wbGVzID0gcGFzdGUwKCJTIiwgMToxNTApKSwgCiAgICAgICAgICAgICAgYnkgPSJzYW1wbGVzIikKCnRhYiAlPiUgCiAgICBkcGx5cjo6c2VsZWN0KC1zYW1wbGVzKSAlPiUgCiAgICBkcGx5cjo6cmVuYW1lKCJ0cnV0aCIgPSBjbHVzdGVyKSAlPiUKICAgIGd0c3VtbWFyeTo6dGJsX3N1bW1hcnkoYnkgPSBzaW1fc29sbikgJT4lIG1vZGlmeV9oZWFkZXIobGFiZWwgPSAiKipzaW11bGF0ZWQgc29sdXRpb24qKiIpCgphcmkgPC0gcm91bmQobWNsdXN0OjphZGp1c3RlZFJhbmRJbmRleCh0YWIkc2ltX3NvbG4sIHRhYiRjbHVzdGVyKSwyKQpgYGAKClRoZSBjb25jb3JkYW5jZSBiZXR3ZWVuIHRydWUgYW5kIHN1cnZDbHVzdCBwcmVkaWN0ZWQgc29sdXRpb24gYXMgY29tcHV0ZWQgYnkgUmFuZCBJbmRleCBpcyBgciBhcmlgCgojIyMgQ29uY2x1c2lvbiAKCiogSW4gdGhpcyBzY2VuYXJpbyB3ZSBwZXJmb3JtZWQgYW4gaW50ZWdyYXRpdmUgc3VwZXJ2aXNlZCBjbHVzdGVyaW5nLiBOb3RlIHRoYXQsIHN1cnZDbHVzdCBjYW4gcGVyZm9ybSBjbHVzdGVyaW5nIG9uIGFzIG1hbnkgZGF0YXNldHMgYXMgcG9zc2libGUgYW5kIGNvbnNpc3RpbmcgYSBtaXggb2YgYmluYXJ5IGFuZCBjb250aW51b3VzIGRhdGEgdHlwZXMuIEluIHRoZSBwdWJsaXNoZWQgd29yaywgc3VydkNsdXN0IHBlcmZvcm1lZCBpbnRlZ3JhdGl2ZSBjbHVzdGVyaW5nIG9uIDYgZGF0YSB0eXBlcy4gCgoqIFRoZSBSYW5kIEluZGV4IGlzIGJldHdlZW4gc3VydkNsdXN0IHNvbHV0aW9uIGFuZCB0aGUgdHJ1dGggaXMgYHIgYXJpYCBtZWFuaW5nIGV2ZW4gdXBvbiBhZGRpdGlvbiBvZiBhIHdlYWtlciBkYXRhIHNldCwgYHN1cnZDbHVzdGAgaXMgYWJsZSB0byBwcmVkaWN0IGEgc29sdXRpb24gY2xvc2UgdG8gdHJ1dGggd2l0aG91dCBhZmZlY3RpbmcgaXRzIHBlcmZvcm1hbmNlIGluIHRoZSBwcmVzZW5jZSBvZiBub2lzZS4gCgojIyBTY2VuYXJpbyAzCgpJbiB0aGlzIGxhc3Qgc2NlbmFyaW8sIHdlIHNpbXVsYXRlIHR3byBkYXRhc2V0cyB3aXRoIDE3NSBzYW1wbGVzIGFuZCAyMDAgZmVhdHVyZXMgc3VjaCB0aGF0IC0gCgpEYXRhIHR5cGUgMSDigJMgc3Ryb25nIGNsdXN0ZXJzIGFuZCB3ZWFrIHN1cnZpdmFsIGFzc29jaWF0aW9uLgoKRGF0YXR5cGUgMiDigJMgd2VhayBjbHVzdGVycyBhbmQgc3Ryb25nIGNsdXN0ZXIgYXNzb2NpYXRpb24uIAoKYGBge3IsIGVjaG89RkFMU0V9CmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKCJpbWFnZXMvc2NlbmFyaW8zLnBuZyIpCmBgYAoKCmBgYHtyfQoKbWF0X3NjZW5hcmlvMyA8LSBsaXN0KCkKCiNkYXRhc2V0IDEKbWF0X3NjZW5hcmlvM1tbMV1dIDwtIHNpbXVsYXRlX2RhdGFfdHlwZShrPTMsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGtfc2l6ZSA9IGMoNTAsNTAsNzUpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuX2ZlYXR1cmVzID0gMjAwLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtdSA9IGMoMCwtMS41LCAxLjUpLCBzZCA9IHJlcCgxLDMpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBlcmNfaW5mb3JtID0gMC4xMCkKCiNkYXRhc2V0IDIKbWF0X3NjZW5hcmlvM1tbMl1dIDwtIHNpbXVsYXRlX2RhdGFfdHlwZShrPTQsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGtfc2l6ZSA9IGMoNTAsNTAsMzAsNDUpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuX2ZlYXR1cmVzID0gMjAwLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtdSA9IGMoMCwtMC41LCAwLjUsIDEuNSksIHNkID0gcmVwKDEsNCksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBlcmNfaW5mb3JtID0gMC4xMCkKI3NpbXVsYXRlIHN1cnZpdmFsIGRhdGEgCnN1cnZfc2NlbmFyaW8zIDwtIHNpbXVsYXRlX3N1cnZfZGF0KGs9NCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGtfc2l6ZSA9IGMoNTAsNTAsMzAsNDUpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWVkX3N1cnZfdGltZXMgPSBjKDUuNSw0LDIuNSwxKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWF4X3N1cnZpdmFsID0gMTAsIG15X3NlZWQ9MTEyKQojdHJ1dGgKdHJ1ZV9zb2xuX3NjZW5hcmlvMyA8LSBkYXRhLmZyYW1lKHNhbXBsZXMgPSBwYXN0ZTAoIlMiLCAxOjE3NSksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY2x1c3RlciA9IGMocmVwKDEsNTApLCByZXAoMiw1MCksIHJlcCgzLDMwKSwgcmVwKDQsNDUpKSkKYGBgCgpIZXJlIHRoZSBzdXJ2aXZhbCBhc3NvY2lhdGlvbiBpcyBzdHJvbmdlciB3aXRoIGRhdGFzZXQyIGFuZCB3ZSBzaW11bGF0ZSBhIHN1cnZpdmFsIGRhdGEgd2l0aCA0IGNsdXN0ZXJzLiBCZWxvdyBpcyB0aGUgc2ltdWxhdGVkIHN1cnZpdmFsIGRpc3RyaWJ1dGlvbiAtIAoKYGBge3IsIGZpZy53aWR0aD00LCBmaWcuaGVpZ2h0PTR9CgpwcCA9IHN1cnZfc2NlbmFyaW8zICU+JQogICAgdGliYmxlOjpyb3duYW1lc190b19jb2x1bW4odmFyID0gInNhbXBsZXMiKSAlPiUKICAgIGxlZnRfam9pbiguLCB0cnVlX3NvbG5fc2NlbmFyaW8zLCBieSA9InNhbXBsZXMiKSAlPiUKICAgIGdnc3VydnBsb3Qoc3VydmZpdChTdXJ2KHRpbWUsIGV2ZW50KSB+IGNsdXN0ZXIsIGRhdGEgPSAuKSwgZGF0YSA9IC4sIAogICAgICAgICAgICAgICBzdXJ2Lm1lZGlhbi5saW5lID0gInYiLCBwYWxldHRlID0gbXlfY29sX3BhbCkgKyAKICAgIGdndGl0bGUoInN1cnZpdmFsIGRpc3RyaWJ1dGlvbiBhY3Jvc3MgXG4gaz00IGNsdXN0ZXJzLCBzY2VuYXJpbzMiKSAKCnBwJHBsb3QgKyB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMSksCiAgICAgICAgICAgICAgICBsZWdlbmQudGV4dCA9IGVsZW1lbnRfdGV4dChzaXplPTYpKQpgYGAKClRoZSBtZWRpYW4gc3Vydml2YWwgcmF0ZXMgb2YgdGhlIHNpbXVsYXRlZCBzdXJ2aXZhbCBkYXRhc2V0IGZyb20gYHN1cnZmaXRgIG1vZGVsIGFyZSBiZWxvdy4gCgpgYGB7cn0KI21lZGlhbiBzdXJ2aXZhbCByYXRlIG1vZGVsIGZpdCAKZml0IDwtIHN1cnZfc2NlbmFyaW8zICU+JQogICAgdGliYmxlOjpyb3duYW1lc190b19jb2x1bW4odmFyID0gInNhbXBsZXMiKSAlPiUKICAgIGxlZnRfam9pbiguLCB0cnVlX3NvbG5fc2NlbmFyaW8zLCBieSA9InNhbXBsZXMiKSAlPiUKICAgIHN1cnZmaXQoU3Vydih0aW1lLCBldmVudCkgfiBjbHVzdGVyLCBkYXRhID0gLikKCmZpdF9tZWRpYW4gPC0gc3VtbWFyeShmaXQpJHRhYmxlWywnbWVkaWFuJ10gCgpkYXRhLmZyYW1lKGNsdXN0ZXIgPSBnc3ViKCJjbHVzdGVyPSIsImMiLG5hbWVzKGZpdF9tZWRpYW4pKSwgCiAgICAgICAgICAgYG1lZGlhbiBzdXJ2aXZhbCh5cnMpYCA9IHJvdW5kKGZpdF9tZWRpYW4sMikpICU+JSAKICBndDo6Z3QoY2FwdGlvbiA9ICJtZWRpYW4gc3Vydml2YWwgdGltZXMgYXJlIGZyb20gYHN1cnZmaXRgIGZpdCIpCgpgYGAKCiMjIyBzdXJ2Q2x1c3QgcnVuIAoKU2ltaWxhciwgdG8gcHJldmlvdXMgc2VjdGlvbiwgY3Jvc3MgdmFsaWRhdGlvbiB3YXMgcGVyZm9ybWVkIGFuZCBwYXJhbGxlbGl6ZWQgZm9yIGVhY2ggay4gCgpgYGB7cn0KcmVnaXN0ZXJEb1BhcmFsbGVsKGNsIDwtIG1ha2VDbHVzdGVyKDYpKQoKcHRtIDwtIFN5cy50aW1lKCkKc2ltM19jdl9maXQgPC0gZm9yZWFjaChraz0yOjcpICVkb3BhciUgc2ltX2N2cm91bmRzKGtrLCBzaW1kYXQgPSBtYXRfc2NlbmFyaW8zLCBzaW1zdXJ2ZGF0ID0gc3Vydl9zY2VuYXJpbzMpCnB0bTIgPC0gU3lzLnRpbWUoKQoKc3RvcENsdXN0ZXIoY2wpCnR0ID0gcHRtMi1wdG0KYGBgCgpUaGUgY3Jvc3MgdmFsaWRhdGlvbiB0b29rIGByIHJvdW5kKGFzLmRvdWJsZSh0dCwgdW5pdHM9Im1pbnMiKSwyKWAgbWludXRlcyB0byBydW4uIAoKIyMjIFJlc3VsdHMgYW5kIGNob29zaW5nIGsKClRoZSBsb2dyYW5rIHRlc3Qgc3RhdGlzdGljIGZvciB0aGlzIHNjZW5hcmlvIGlzIG1heGltdW0gYXQgYGs9NGAgYW5kIHdlIHNlZSB0aGF0IHRoZSBTUFdTUyBjdXJ2ZSAiZWxib3dzIiBhdCBgaz00YC4gVGhlIGNyb3NzIHZhbGlkYXRpb24gcm91bmQgb2YgYGs9NGAgYWxzbyBkaWQgbm90IGhhdmUgYW55IGNsdXN0ZXIgc2l6ZXMgd2l0aCA8PTUgc2FtcGxlcy4gVGh1cywgd2Ugd2lsbCBjaG9vc2UgYGs9NGAgYXMgdGhlIG9wdGltYWwgay4gIAoKYGBge3J9CnNzX3N0YXRzIDwtIGdldFN0YXRzKHNpbTNfY3ZfZml0LCBraz03LCBjdnI9MTApCgpwX2xyIDwtIHBsb3RTdGF0c190aWR5KHNzX3N0YXRzJGxyLCBoaWdobGlnaHRfayA9IDQpCnBfbHIgPC0gcF9sciArIHlsYWIoImxvZ3JhbmsgdGVzdCBzdGF0aXN0aWMiKQoKcF9zcHdzcyA8LSBwbG90U3RhdHNfdGlkeShzc19zdGF0cyRzcHdzcywgaGlnaGxpZ2h0X2sgPSA0KQpwX3Nwd3NzIDwtIHBfc3B3c3MgKyB5bGFiKCJTUFdTUyIpCgpwX2NsdXNpemUgPC0gZGF0YS5mcmFtZShrID0gcGFzdGUwKCJrIiwyOjcpLCBjdl9sZXNzX3RoYW5fNSA9IHNzX3N0YXRzJGJhZC5zb2wpICU+JQogICAgZ2dwbG90KGFlcyh4PWssIHkgPSBjdl9sZXNzX3RoYW5fNSkpICsgZ2VvbV9wb2ludCgpICsgZ2VvbV9saW5lKGNvbCA9ICJza3libHVlIiwgZ3JvdXA9ImsiKSArIHRoZW1lX21pbmltYWwoKSArIHlsYWIoImN2IHNvbHV0aW9uIHdpdGggPD01IHNhbXBsZXMiKQpgYGAKCgpgYGB7ciwgZmlnLndpZHRoPTgsIGZpZy5oZWlnaHQ9OH0KZ2dhcnJhbmdlKHBfbHIsIHBfc3B3c3MsIHBfY2x1c2l6ZSwgbnJvdz0yLCBuY29sPTIpCmBgYAoKCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQprNCA8LSBjdl92b3RpbmcoY3YuZml0ID0gc2ltM19jdl9maXQsCiAgICAgICAgICAgICAgICBkYXQuZGlzdCA9IGdldERpc3QoZGF0YXNldHMgPSBtYXRfc2NlbmFyaW8zLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN1cnZkYXQgPSBzdXJ2X3NjZW5hcmlvMyksIAogICAgICAgICAgICAgICAgcGlja19rPTQpCmBgYAoKIyMjIyBrPTQKCkZyb20gdGhlIG1ldHJpY3MgYWJvdmUsIHdlIGNob29zZSBgaz00YCBhcyBvdXIgb3B0aW1hbCBrLiBXZSBjYW4gY29tcGFyZSB0aGlzIHdpdGggb3VyIHNpbXVsYXRlZCB0cnVlIHNvbHV0aW9uIC0gCgpgYGB7cn0KCnRhYiA8LSB0cnVlX3NvbG5fc2NlbmFyaW8zICU+JSAKICAgIGxlZnRfam9pbiguLCBkYXRhLmZyYW1lKHNpbV9zb2xuID0gazQsIHNhbXBsZXMgPSBwYXN0ZTAoIlMiLCAxOjE3NSkpLCAKICAgICAgICAgICAgICBieSA9ICJzYW1wbGVzIikKCnRhYiAlPiUgCiAgICBkcGx5cjo6c2VsZWN0KC1zYW1wbGVzKSAlPiUgCiAgICBkcGx5cjo6cmVuYW1lKCJ0cnV0aCIgPSBjbHVzdGVyKSAlPiUKICAgIGd0c3VtbWFyeTo6dGJsX3N1bW1hcnkoYnkgPSBzaW1fc29sbikgJT4lIG1vZGlmeV9oZWFkZXIobGFiZWwgPSAiKipzaW11bGF0ZWQgc29sdXRpb24qKiIpCgphcmkgPC0gcm91bmQobWNsdXN0OjphZGp1c3RlZFJhbmRJbmRleCh0YWIkc2ltX3NvbG4sIHRhYiRjbHVzdGVyKSwyKQpgYGAKClRoZSBjb25jb3JkYW5jZSBiZXR3ZWVuIHRydWUgYW5kIHN1cnZDbHVzdCBwcmVkaWN0ZWQgc29sdXRpb24gYXMgY29tcHV0ZWQgYnkgUmFuZCBJbmRleCBpcyBgciBhcmlgCgojIyMgQ29uY2x1c2lvbiAKCkluIHRoaXMgc2NlbmFyaW8gd2Ugb2JzZXJ2ZSB0aGF0IHN1cnZDbHVzdCB3YXMgYWJsZSB0byBhcnJpdmUgYXQgc29sdXRpb24gc2hvd2luZyBnb29kIGNvbmNvcmRhbmNlIHdpdGggdGhlIHRydXRoIGV2ZW4gd2hlbiB3ZWFrZXIgY2x1c3RlcnMgc2hvdyBiZXR0ZXIgYXNzb2NpYXRpb24gd2l0aCBzdXJ2aXZhbC4gCgojIERpc2N1c3Npb24gIAoKSGVyZSB3ZSBkZW1vbnN0cmF0ZWQgc3VydkNsdXN0LCBhIHN1cGVydmlzZWQgY2x1c3RlcmluZyBtZXRob2RvbG9neSB0aGF0IGNhbiBpbmNvcnBvcmF0ZSBzdXJ2aXZhbCBpbmZvcm1hdGlvbiBhcyB3ZWlnaHRzIGJ1dCBhbHNvIGludGVncmF0ZSBkYXRhIGZyb20gbXVsdGlwbGUgbW9kYWxpdGllcy4gV2UgbG9va2VkIGF0IHZhcmlvdXMgc2NlbmFyaW9zIGFuZCBoaWdobGlnaHRlZCBob3cgYHN1cnZDbHVzdGAgd2FzIGFibGUgdG8gcHJlZGljdCBzb2x1dGlvbnMgaW4gaGlnaCBjb25jb3JkYW5jZSB3aXRoIHRoZSB0cnVlIGNsYXNzIGxhYmVscyB0aGF0IHdlIHNpbXVsYXRlZC4gCgpIb3dldmVyLCB0aGVyZSB3ZXJlIGFkZGl0aW9uYWwgc2ltdWxhdGluZyBzdHJhdGVnaWVzIHRoYXQgb25lIGNvdWxkIGZ1cnRoZXIgYXBwbHkgLSAKCjEuIEF0IHByZXNlbnQgYSBjbHVzdGVyIGNvbnRhaW5zIGZlYXR1cmVzIHRoYXQgYXJlIGFzc29jaWF0ZWQgd2l0aCBzdXJ2aXZhbCBpbiBvbmUgZGlyZWN0aW9uIG9ubHkuIEluIG1vcmUgdGhvcm91Z2ggd29yayB0b3dhcmRzIHRoZSBmaW5hbCBwdWJsaWNhdGlvbiwgSSBzaW11bGF0ZWQgY2x1c3RlciBzdHJ1Y3R1cmVzIHRoYXQgaGFkIGZlYXR1cmVzIHRoYXQgaGFkIGEgbWl4IG9mIGZlYXR1cmVzIHRoYXQgY29udHJpYnV0ZWQgdG8gd29yc2UgYW5kIGJldHRlciBzdXJ2aXZhbCBhc3NvY2lhdGlvbi4gCgoyLiBTaW11bGF0aW5nIHN1cnZpdmFsIGRpc3RyaWJ1dGlvbiBvbiBhIHNtYWxsIHNhbXBsZSBzaXplIGlzIGNoYWxsZW5naW5nIGFuZCBtaWdodCB2aW9sYXRlIHRoZSBhc3N1bXB0aW9uIG9mIHByb3BvcnRpb25hbCBoYXphcmRzLiBUaGlzIGNvdWxkIGV4cGxhaW4gd2h5IGZvciBzb21lIHNjZW5hcmlvcyB3ZSBhcmUgbWlzY2xhc3NpZnlpbmcgc29tZSBzYW1wbGVzLiAKCjMuIGBzZXQuc2VlZGAgZnVuY3Rpb25hbGl0eSBwcm92aWRlZCB2aWEgYG15X3NlZWRgIHBhcmFtZXRlciBzaG91bGQgYmUgdXNlZCBjYXJlZnVsbHksIGFzIG9uZSBtaWdodCBiZSBkdXBsaWNhdGluZyBkYXRhIHdoaWxlIHNpbXVsYXRpbmcuIEZvciBleGFtcGxlLCBpZiB3ZSB3YW50IHRvIHNpbXVsYXRlIDYgY2x1c3RlcnMgYW5kIG91dCBvZiB3aGljaCAyIGFyZSBzaW1pbGFyIGluIHNpemUgYW5kIGRpc3RyaWJ1dGlvbiBidXQgbWlnaHQgdmFyeSBpbiB0ZXJtcyBvZiBzdXJ2aXZhbCwgYSBjYXNlIHdlIG9mdGVuIHNlZSB3aXRoIHJlYWwgZGF0YS4gCgpzdXJ2Q2x1c3QgY29udGludWVzIHRvIGJlIHVzZWQgYnkgbWFueSByZXNlYXJjaGVycyBpbiB0aGUgc2NpZW50aWZpYyBjb21tdW5pdHkuIAoKIyMgSXMgaW50ZWdyYXRpb24gYWx3YXlzIGJldHRlcj8gCgpzdXJ2Q2x1c3QgY2FuIGJlIHJ1biBpbmRpdmlkdWFsbHkgaW4gbXVsdGlwbGUgZGF0YXNldHMgb3IgaW4gYW4gaW50ZWdyYXRlZCBmYXNoaW9uIGFjcm9zcyBkaWZmZXJlbnQgbW9kYWxpdGllcyBwcm9maWxlZCBmb3IgY29uc3RhbnQgc3ViamVjdHMuIEFjY29yZGluZyB0byB0aGUgbmF0dXJlIGFuZCBxdWFsaXR5IG9mIHRoZXNlIGRhdGFzZXRzLCBvbmUgbWlnaHQgcGVyZm9ybSBiZXR0ZXIgb3Igd29yc2UgYnkgaW50ZWdyYXRpbmcgYWxsIHRoZSBhdmFpbGFibGUgaW5mb3JtYXRpb24uIFdlIHJhbiBzdXJ2Q2x1c3Qgb24gaW5kaXZpZHVhbCBkYXRhc2V0cyBmcm9tIHNjZW5hcmlvcyAyIGFuZCAzIGFuZCBjb21wYXJlIHRoZW0gd2l0aCB0aGUgaW50ZWdyYXRlZCBydW4gYXMgcmVwb3J0ZWQgYWJvdmUuIAoKYGBge3J9CgpyZWdpc3RlckRvUGFyYWxsZWwoY2wgPC0gbWFrZUNsdXN0ZXIoNikpCgpkYXQ8LWxpc3QoKQpkYXRbWzFdXSA8LSBtYXRfc2NlbmFyaW8yW1sxXV0Kc2ltMl9jdl9maXQxIDwtIGZvcmVhY2goa2s9Mjo3KSAlZG9wYXIlIHNpbV9jdnJvdW5kcyhraywgc2ltZGF0ID0gZGF0LCBzaW1zdXJ2ZGF0ID0gc3Vydl9zY2VuYXJpbzIpCgoKZGF0PC1saXN0KCkKZGF0W1sxXV0gPC0gbWF0X3NjZW5hcmlvMltbMl1dCnNpbTJfY3ZfZml0MiA8LSBmb3JlYWNoKGtrPTI6NykgJWRvcGFyJSBzaW1fY3Zyb3VuZHMoa2ssIHNpbWRhdCA9IGRhdCwgc2ltc3VydmRhdCA9IHN1cnZfc2NlbmFyaW8yKQoKCmRhdDwtbGlzdCgpCmRhdFtbMV1dIDwtIG1hdF9zY2VuYXJpbzNbWzFdXQpzaW0zX2N2X2ZpdDEgPC0gZm9yZWFjaChraz0yOjcpICVkb3BhciUgc2ltX2N2cm91bmRzKGtrLCBzaW1kYXQgPSBkYXQsIHNpbXN1cnZkYXQgPSBzdXJ2X3NjZW5hcmlvMykKCgpkYXQ8LWxpc3QoKQpkYXRbWzFdXSA8LSBtYXRfc2NlbmFyaW8zW1syXV0Kc2ltM19jdl9maXQyIDwtIGZvcmVhY2goa2s9Mjo3KSAlZG9wYXIlIHNpbV9jdnJvdW5kcyhraywgc2ltZGF0ID0gZGF0LCBzaW1zdXJ2ZGF0ID0gc3Vydl9zY2VuYXJpbzMpCgpzdG9wQ2x1c3RlcihjbCkKCmFsbF9zdGF0cyA8LSBwdXJycjo6bWFwKGMoInNpbTJfY3ZfZml0MSIsICJzaW0yX2N2X2ZpdDIiLCAic2ltM19jdl9maXQxIiwgInNpbTNfY3ZfZml0MiIpLCBmdW5jdGlvbih4KSBnZXRTdGF0cyhnZXQoeCksIGtrPTcsIGN2cj0xMCkpCgpuYW1lcyhhbGxfc3RhdHMpID0gYygic2ltMl9jdl9maXQxIiwgInNpbTJfY3ZfZml0MiIsICJzaW0zX2N2X2ZpdDEiLCAic2ltM19jdl9maXQyIikKCnNpbTJfc3RhdHMgPC0gZ2V0U3RhdHMoc2ltMl9jdl9maXQsIGtrPTcsIGN2ciA9IDEwKQpzaW0zX3N0YXRzIDwtIGdldFN0YXRzKHNpbTNfY3ZfZml0LCBraz03LCBjdnI9MTApCgpzY2VuYXJpbzJfaW50ZWcgPC0gbWFrZV9pbnRlZ3JhdGVkX2RhdGEoZGF0MSA9IGFsbF9zdGF0cyRzaW0yX2N2X2ZpdDEsIGRhdDIgPSBhbGxfc3RhdHMkc2ltMl9jdl9maXQyLCBpbnRlZyA9IHNpbTJfc3RhdHMpCgpzY2VuYXJpbzJfaW50ZWdfcGxvdHMgPC0gbWFrZV9pbnRlZ3JhdGVkX3Bsb3RzKHNjZW5hcmlvMl9pbnRlZykKCnBwIDwtIGdnYXJyYW5nZShwbG90bGlzdCA9IHNjZW5hcmlvMl9pbnRlZ19wbG90cywgbmNvbD0xKQpwcDIgPC0gYW5ub3RhdGVfZmlndXJlKHBwLCB0b3AgPSB0ZXh0X2dyb2IoIlNjZW5hcmlvMiIsIGZhY2UgPSAiYm9sZCIpKQoKCnNjZW5hcmlvM19pbnRlZyA8LSBtYWtlX2ludGVncmF0ZWRfZGF0YShkYXQxID0gYWxsX3N0YXRzJHNpbTNfY3ZfZml0MSwgZGF0MiA9IGFsbF9zdGF0cyRzaW0zX2N2X2ZpdDIsIGludGVnID0gc2ltM19zdGF0cykKCnNjZW5hcmlvM19pbnRlZ19wbG90cyA8LSBtYWtlX2ludGVncmF0ZWRfcGxvdHMoc2NlbmFyaW8zX2ludGVnKQoKcHAgPC0gZ2dhcnJhbmdlKHBsb3RsaXN0ID0gc2NlbmFyaW8zX2ludGVnX3Bsb3RzLCBuY29sPTEpCnBwMyA8LSBhbm5vdGF0ZV9maWd1cmUocHAsIHRvcCA9IHRleHRfZ3JvYigiU2NlbmFyaW8zIiwgZmFjZSA9ICJib2xkIikpCmBgYAoKYGBge3IsIGZpZy53aWR0aD05LCBmaWcuaGVpZ2h0PTl9CmdnYXJyYW5nZShwcDIsIHBwMykKYGBgCgpJbiB0aGUgcGxvdHMgYWJvdmUsIHdlIHBsb3QgdGhlIG1lZGlhbiB2YWx1ZSBhY3Jvc3MgY3Jvc3MgdmFsaWRhdGVkIHJvdW5kcyBmb3IgZWFjaCBrIGZvciB0aGF0IHBhcnRpY3VsYXIgbWV0cmljIChsb2dyYW5rLCBTUFdTUyBldGMuKS4gVGhlIGJhcnMgcmVwcmVzZW50IHRoZSAyNXRoIGFuZCA3NXRoIHF1YW50aWxlcyBvZiB0aGUgZGF0YS4gCgpJbiBzY2VuYXJpbyAyLCB3ZSBhZGRlZCBhIG5vaXN5IGRhdGEgdHlwZSBhcyBkYXRhc2V0IDIgYW5kIHdlIHNlZSB0aGF0IGRhdGFzZXQgMiBwZXJmb3JtcyB3b3JzZSB0aGFuIGludGVncmF0ZWQgYW5kIGRhdGFzZXQxLiBEYXRhc2V0MSBjYXJyaWVkIHRoZSBzdHJvbmcgY2x1c3RlciBkaXN0aW5jdGlvbiBhbmQgaXMgZG9pbmcgYXMgd2VsbCBhcyBpbnRlZ3JhdGVkIHN1cnZDbHVzdCBzb2x1dGlvbiBpbiB0ZXJtcyBvZiBwZWFrIGxvZ3JhbmsgYXQgYGs9M2AuIAoKQSBzaW1pbGFyIHRyZW5kIGlzIHNlZW4gZm9yIHNjZW5hcmlvIDMsIHdoZXJlIGRhdGFzZXQxIGhhZCBzdHJvbmcgY2x1c3RlciBzZXBhcmF0aW9uIGJ1dCBwb29yIHN1cnZpdmFsIGFzc29jaWF0aW9uIHdpdGggb3ZlcmFsbCBsb3cgbWVkaWFuIGxvZ3JhbmsgdmFsdWVzLiBXaGVyZWFzLCBpbnRlZ3JhdGVkIHNvbHV0aW9uIGFuZCBkYXRhc2V0MiBhcmUgcGVyZm9ybWluZyBzaW1pbGFyLiAKCk92ZXJhbGwsIHdlIHNlZSB0aGF0IGludGVncmF0ZWQgZGF0YSB0eXBlIHBlcmZvcm1zIGFzIHdlbGwgb3Igc2xpZ2h0bHkgYmV0dGVyIHRoYW4gdGhlIGRhdGFzZXQgY2FycnlpbmcgdGhlIHN0cm9uZyBhc3NvY2lhdGlvbiB3aXRoIHN1cnZpdmFsLiAKClteMV06IEFyb3JhLCBBLiwgZXQgYWwuIFBhbi1jYW5jZXIgaWRlbnRpZmljYXRpb24gb2YgY2xpbmljYWxseSByZWxldmFudCBnZW5vbWljIHN1YnR5cGVzIHVzaW5nIG91dGNvbWUtd2VpZ2h0ZWQgaW50ZWdyYXRpdmUgY2x1c3RlcmluZy4gR2Vub21lIE1lZGljaW5lIDEyLjEoMjAyMCk6IDEtMTMKClteMl06IEhvYWRsZXksIEthdGhlcmluZSBBLiwgZXQgYWwuICJDZWxsLW9mLW9yaWdpbiBwYXR0ZXJucyBkb21pbmF0ZSB0aGUgbW9sZWN1bGFyIGNsYXNzaWZpY2F0aW9uIG9mIDEwLDAwMCB0dW1vcnMgZnJvbSAzMyB0eXBlcyBvZiBjYW5jZXIuIiBDZWxsIDE3My4yICgyMDE4KTogMjkxLTMwNAoKW14zXTogSGFycmluZ3RvbiBEUCwgRmxlbWluZyBUUi4gQSBjbGFzcyBvZiByYW5rIHRlc3QgcHJvY2VkdXJlcyBmb3IgY2Vuc29yZWQgc3Vydml2YWwtZGF0YS4gQmlvbWV0cmlrYS4gMTk4Mjs2OTo1NTPigJM2Ni4gCgpbXjRdOiBUaWJzaGlyYW5pIFIsIFdhbHRoZXIgRywgSGFzdGllIFQuIEVzdGltYXRpbmcgdGhlIG51bWJlciBvZiBjbHVzdGVycyBpbiBhIGRhdGEgc2V0IHZpYSB0aGUgZ2FwIHN0YXRpc3RpYy4gSiBSb3kgU3RhdCBTb2MgQi4gMjAwMTs2Mzo0MTHigJMyMy4gCgojIFNlc3Npb24gSW5mbyAKCmBgYHtyIHNlc3Npb25JbmZvLCBjYWNoZT1GQUxTRX0KCiNlbnZpcm9uZW1udCBhbmQgcGFja2FnZSBkZXRhaWxzIApwYW5kZXI6OnBhbmRlcihzZXNzaW9uSW5mbygpKQoKewogICMgYWRkaXRpb25hbCBkZXRhaWxzIAogIGNhdCgiXG5cbiIpCiAgY2F0KHNwcmludGYoIioqU291cmNlOioqIColcyBpbiAlcyBwcm9qZWN0KiBcbiIsIAogICAgICAgICAgICAgIGtuaXRyOjpjdXJyZW50X2lucHV0KGRpcj1GQUxTRSksIHRyeUNhdGNoKHtyc3R1ZGlvYXBpOjpnZXRBY3RpdmVQcm9qZWN0KCl9LCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBlcnJvciA9IGZ1bmN0aW9uKGUpIHtOQX0pICkpCiAgY2F0KHNwcmludGYoIioqS25pdDoqKiAqJXMgYnkgJXMqIFxuIiwgZm9ybWF0KFN5cy50aW1lKCksICclWS0lbS0lZCAlSDolTTolUyVaJyksIFN5cy5pbmZvKClbWyJ1c2VyIl1dICkpCiAgY2F0KHRyeUNhdGNoKHtzcHJpbnRmKCIqKkdpdCByZW1vdGU6KiogKlslc10oJXMpe3RhcmdldD0nX2JsYW5rJ30qIFslc11cbiIsIGdpdDJyOjpyZW1vdGVfdXJsKCksIGdpdDJyOjpyZW1vdGVfdXJsKCksIGdpdDJyOjpjb21taXRzKGdpdDJyOjpyZXBvc2l0b3J5KCIuIiksIG49MSlbWzFdXVtbInNoYSJdXSl9LCBlcnJvciA9IGZ1bmN0aW9uKGUpIHtOVUxMfSkpCn0KCgoKYGBg